From c3f176b39ddb42896e9feea5a7e163f7948d00ff Mon Sep 17 00:00:00 2001 From: Max Thirouin Date: Mon, 16 Dec 2019 16:31:27 +0100 Subject: [PATCH] v5 WIP (#20) * First run at v5 bindings * fix: add missing Interop.js * fix: add route to navigation component props * fix: add route type to functor --- src/BottomTabs.bs.js | 42 +++++ src/BottomTabs.re | 180 ++++++++++++++++++ src/Core.bs.js | 72 ++++++++ src/Core.re | 126 +++++++++++++ src/Drawer.bs.js | 42 +++++ src/Drawer.re | 172 +++++++++++++++++ src/Example.bs.js | 131 +++++++++++++ src/Example.re | 60 ++++++ src/Interop.js | 1 + src/MaterialBottomTabs.bs.js | 62 +++++++ src/MaterialBottomTabs.re | 135 ++++++++++++++ src/MaterialTopTabs.bs.js | 42 +++++ src/MaterialTopTabs.re | 202 ++++++++++++++++++++ src/Native.bs.js | 7 + src/Native.re | 15 ++ src/Stack.bs.js | 73 ++++++++ src/Stack.re | 346 +++++++++++++++++++++++++++++++++++ 17 files changed, 1708 insertions(+) create mode 100644 src/BottomTabs.bs.js create mode 100644 src/BottomTabs.re create mode 100644 src/Core.bs.js create mode 100644 src/Core.re create mode 100644 src/Drawer.bs.js create mode 100644 src/Drawer.re create mode 100644 src/Example.bs.js create mode 100644 src/Example.re create mode 100644 src/Interop.js create mode 100644 src/MaterialBottomTabs.bs.js create mode 100644 src/MaterialBottomTabs.re create mode 100644 src/MaterialTopTabs.bs.js create mode 100644 src/MaterialTopTabs.re create mode 100644 src/Native.bs.js create mode 100644 src/Native.re create mode 100644 src/Stack.bs.js create mode 100644 src/Stack.re diff --git a/src/BottomTabs.bs.js b/src/BottomTabs.bs.js new file mode 100644 index 00000000..cefb0a72 --- /dev/null +++ b/src/BottomTabs.bs.js @@ -0,0 +1,42 @@ +'use strict'; + +var Core$ReactNavigation = require("./Core.bs.js"); +var BottomTabs = require("@react-navigation/bottom-tabs"); + +function BottomTabNavigationProp(M) { + var include = Core$ReactNavigation.NavigationScreenProp(M); + return { + navigateByKey: include.navigateByKey, + navigateByName: include.navigateByName + }; +} + +function Make(M) { + var M$1 = { }; + var include = Core$ReactNavigation.NavigationScreenProp(M$1); + var Navigation_navigateByKey = include.navigateByKey; + var Navigation_navigateByName = include.navigateByName; + var Navigation = { + navigateByKey: Navigation_navigateByKey, + navigateByName: Navigation_navigateByName + }; + var stack = BottomTabs.createBottomTabNavigator(); + var make = stack.Screen; + var $$Screen = { + make: make + }; + var make$1 = stack.Navigator; + var $$Navigator = { + make: make$1 + }; + return { + Navigation: Navigation, + stack: stack, + Screen: $$Screen, + Navigator: $$Navigator + }; +} + +exports.BottomTabNavigationProp = BottomTabNavigationProp; +exports.Make = Make; +/* @react-navigation/bottom-tabs Not a pure module */ diff --git a/src/BottomTabs.re b/src/BottomTabs.re new file mode 100644 index 00000000..53ebd1e7 --- /dev/null +++ b/src/BottomTabs.re @@ -0,0 +1,180 @@ +open Core; + +type options; + +module BottomTabNavigationProp = (M: { + type params; + type options; + }) => { + include NavigationScreenProp(M); + + type t = navigation; + + [@bs.send] external jumpTo: (t, string) => unit = "jumpTo"; + [@bs.send] + external jumpToWithParams: (t, string, M.params) => unit = "jumpTo"; +}; + +module Make = (M: {type params;}) => { + module Navigation = + BottomTabNavigationProp({ + include M; + type nonrec options = options; + }); + + type animatedNode = ReactNative.Animated.Value.t; + + type scene = { + . + "index": int, + "focused": bool, + "tintColor": string, + }; + + class type virtual baseBottomTabBarOptions = { + pub keyboardHidesTabBar: option(bool); + pub activeBackgroundColor: option(string); + pub inactiveBackgroundColor: option(string); + pub allowFontScaling: option(bool); + pub showLabel: option(bool); + pub showIcon: option(bool); + pub labelStyle: option(ReactNative.Style.t); + pub tabStyle: option(ReactNative.Style.t); + pub labelPosition: option({. "deviceOrientation": string} => string); + pub adaptive: option(bool); + pub style: option(ReactNative.Style.t); + }; + + class type virtual bottomTabBarOptions = { + as 'self; + constraint 'self = #baseBottomTabBarOptions; + pub activeTintColor: option(string); + pub inactiveTintColor: option(string); + }; + + type accessibilityRole = string; + type accessibilityStates = array(string); + class type virtual bottomTabBarProps = { + as 'self; + constraint 'self = #baseBottomTabBarOptions; + pub state: navigationState(M.params); + pub navigation: navigation; + pub onTabPress: {. "route": route(M.params)} => unit; + pub onTabLongPress: {. "route": route(M.params)} => unit; + pub getAccessibilityLabel: + {. "route": route(M.params)} => Js.nullable(string); + pub getAccessibilityRole: + {. "route": route(M.params)} => Js.nullable(accessibilityRole); + pub getAccessibilityStates: + {. "route": route(M.params)} => Js.nullable(accessibilityStates); + pub getButtonComponent: + {. "route": route(M.params)} => Js.nullable(React.element); + //pub getLabelText: {. "route": route(M.params)} => ...; + pub getTestID: {. "route": route(M.params)} => Js.nullable(string); + pub renderIcon: + { + . + "route": route(M.params), + "focused": bool, + "tintColor": string, + "horizontal": bool, + } => + React.element; + pub activeTintColor: string; + pub inactiveTintColor: string; + }; + + [@bs.obj] + external bottomTabBarOptions: + ( + ~keyboardHidesTabBar: bool=?, + ~activeTintColor: string=?, + ~inactiveTintColor: string=?, + ~activeBackgroundColor: string=?, + ~inactiveBackgroundColor: string=?, + ~allowFontScaling: bool=?, + ~showLabel: bool=?, + ~showIcon: bool=?, + ~labelStyle: ReactNative.Style.t=?, + ~tabStyle: ReactNative.Style.t=?, + ~labelPosition: {. "deviceOrientation": string} => string=?, + ~adaptive: bool=?, + ~style: ReactNative.Style.t=? + ) => + bottomTabBarOptions = + ""; + + [@bs.obj] + external options: + ( + ~title: string=?, + //TODO: dynamic, missing static option: React.ReactNode + ~tabBarLabel: scene => React.element=?, + //TODO: dynamic, missing static option: React.ReactNode + ~tabBarIcon: scene => React.element=?, + ~tabBarAccessibilityLabel: string=?, + ~tabBarTestID: string=?, + ~tabBarVisible: bool=?, + ~tabBarButtonComponent: React.element=?, + unit + ) => + options = + ""; + + type optionsProps = + { + . + "navigation": navigation, + "route": route(M.params), + } => + options; + + type navigatorProps; + + type screenProps; + + [@bs.module "@react-navigation/bottom-tabs"] + external make: + unit => + { + . + "Navigator": navigatorProps => React.element, + "Screen": screenProps => React.element, + } = + "createBottomTabNavigator"; + + let stack = make(); + + module Screen = { + [@bs.obj] + external makeProps: + ( + ~name: string, + ~options: optionsProps=?, + ~initialParams: M.params=?, + ~component: React.component({. "navigation": navigation}), + unit + ) => + screenProps = + ""; + let make = stack##"Screen"; + }; + + module Navigator = { + [@bs.obj] + external makeProps: + ( + ~initialRouteName: string=?, + ~screenOptions: optionsProps=?, + ~children: React.element, + ~_lazy: bool=?, + ~tabBarComponent: React.component(Js.t(bottomTabBarProps))=?, + ~tabBarOptions: bottomTabBarOptions=?, + unit + ) => + navigatorProps = + ""; + + let make = stack##"Navigator"; + }; +}; diff --git a/src/Core.bs.js b/src/Core.bs.js new file mode 100644 index 00000000..eb81f3a6 --- /dev/null +++ b/src/Core.bs.js @@ -0,0 +1,72 @@ +'use strict'; + +var Caml_option = require("bs-platform/lib/js/caml_option.js"); + +function NavigationHelpersCommon(M) { + var navigateByKey = function (key, params, unit) { + var tmp = { + key: key + }; + if (params !== undefined) { + tmp.params = Caml_option.valFromOption(params); + } + tmp.navigate(); + return /* () */0; + }; + var navigateByName = function (name, key, params, unit) { + var tmp = { + name: name + }; + if (key !== undefined) { + tmp.key = Caml_option.valFromOption(key); + } + if (params !== undefined) { + tmp.params = Caml_option.valFromOption(params); + } + tmp.navigate(); + return /* () */0; + }; + return { + navigateByKey: navigateByKey, + navigateByName: navigateByName + }; +} + +function EventConsumer(M) { + return { }; +} + +function NavigationScreenProp(M) { + var navigateByKey = function (key, params, unit) { + var tmp = { + key: key + }; + if (params !== undefined) { + tmp.params = Caml_option.valFromOption(params); + } + tmp.navigate(); + return /* () */0; + }; + var navigateByName = function (name, key, params, unit) { + var tmp = { + name: name + }; + if (key !== undefined) { + tmp.key = Caml_option.valFromOption(key); + } + if (params !== undefined) { + tmp.params = Caml_option.valFromOption(params); + } + tmp.navigate(); + return /* () */0; + }; + return { + navigateByKey: navigateByKey, + navigateByName: navigateByName + }; +} + +exports.NavigationHelpersCommon = NavigationHelpersCommon; +exports.EventConsumer = EventConsumer; +exports.NavigationScreenProp = NavigationScreenProp; +/* No side effect */ diff --git a/src/Core.re b/src/Core.re new file mode 100644 index 00000000..bf069b44 --- /dev/null +++ b/src/Core.re @@ -0,0 +1,126 @@ +type route('params) = { + . + "key": string, + "name": string, + "params": option('params), +}; + +type navigationState('params) = { + . + "key": string, + "index": int, + "routeNames": array(string), + "routes": + array({ + . + "key": string, + "name": string, + "params": option('params), + "state": option(navigationState('params)), + }), +}; + +type navigation; + +module NavigationHelpersCommon = (M: {type params;}) => { + type nonrec route = route(M.params); + [@bs.send] + external dispatch: (navigation, NavigationActions.action) => unit = ""; + + [@bs.send] external navigate: (navigation, string) => unit = "navigate"; + [@bs.send] + external navigateWithParams: (navigation, string, M.params) => unit = + "navigate"; + + type navigationParams; + + [@bs.obj] + external navigateByKeyParams: + (~key: string, ~params: M.params=?, unit) => navigationParams = + ""; + + [@bs.obj] + external navigateByNameParams: + (~name: string, ~key: string=?, ~params: M.params=?, unit) => + navigationParams = + ""; + + [@bs.send] external navigateBy: navigationParams => unit = "navigate"; + + let navigateByKey = (~key: string, ~params: option(M.params)=?, unit) => + navigateBy(navigateByKeyParams(~key, ~params?, ())); + + let navigateByName = + ( + ~name: string, + ~key: option(string)=?, + ~params: option(M.params)=?, + unit, + ) => + navigateBy(navigateByNameParams(~name, ~key?, ~params?, ())); + + [@bs.send] external replace: (navigation, string) => unit = "replace"; + [@bs.send] + external replaceWithParams: (navigation, string, M.params) => unit = + "replace"; + + [@bs.send] + external reset: (navigation, navigationState(M.params)) => unit = "reset"; + + [@bs.send] + external resetRoot: (navigation, navigationState(M.params)) => unit = + "resetRoot"; + + [@bs.send] external goBack: (navigation, unit) => unit = "goBack"; + [@bs.send] external isFocused: (navigation, unit) => bool = "isFocused"; + [@bs.send] external canGoBack: (navigation, unit) => bool = "canGoBack"; +}; + +module EventConsumer = (M: {type params;}) => { + type eventListenerCallback('data) = + { + . + "type": string, + "defaultPrevented": bool, + [@bs.meth] "preventDefault": unit => unit, + "data": option('data), + } => + unit; + + type unsubscribe = unit => unit; + + [@bs.send] + external addListener: + ( + navigation, + [@bs.string] [ | `focus | `blur | `tabPress], + eventListenerCallback('data) + ) => + unsubscribe = + ""; + [@bs.send] + external removeListener: + ( + navigation, + [@bs.string] [ | `focus | `blur | `tabPress], + eventListenerCallback('data) + ) => + unsubscribe = + ""; +}; + +module NavigationScreenProp = (M: { + type params; + type options; + }) => { + include NavigationHelpersCommon(M); + include EventConsumer(M); + + [@bs.send] external setParams: (navigation, M.params) => unit = ""; + [@bs.send] external setOptions: (navigation, M.options) => unit = ""; + + [@bs.send] external isFirstRouteInParent: (navigation, unit) => bool = ""; + + [@bs.send] + external dangerouslyGetParent: navigation => Js.nullable(navigation) = ""; +}; diff --git a/src/Drawer.bs.js b/src/Drawer.bs.js new file mode 100644 index 00000000..33968fda --- /dev/null +++ b/src/Drawer.bs.js @@ -0,0 +1,42 @@ +'use strict'; + +var Core$ReactNavigation = require("./Core.bs.js"); +var Drawer = require("@react-navigation/drawer"); + +function DrawerNavigationProp(M) { + var include = Core$ReactNavigation.NavigationScreenProp(M); + return { + navigateByKey: include.navigateByKey, + navigateByName: include.navigateByName + }; +} + +function Make(M) { + var M$1 = { }; + var include = Core$ReactNavigation.NavigationScreenProp(M$1); + var Navigation_navigateByKey = include.navigateByKey; + var Navigation_navigateByName = include.navigateByName; + var Navigation = { + navigateByKey: Navigation_navigateByKey, + navigateByName: Navigation_navigateByName + }; + var stack = Drawer.createDrawerNavigator(); + var make = stack.Screen; + var $$Screen = { + make: make + }; + var make$1 = stack.Navigator; + var $$Navigator = { + make: make$1 + }; + return { + Navigation: Navigation, + stack: stack, + Screen: $$Screen, + Navigator: $$Navigator + }; +} + +exports.DrawerNavigationProp = DrawerNavigationProp; +exports.Make = Make; +/* @react-navigation/drawer Not a pure module */ diff --git a/src/Drawer.re b/src/Drawer.re new file mode 100644 index 00000000..5b2d499e --- /dev/null +++ b/src/Drawer.re @@ -0,0 +1,172 @@ +open Core; + +type options; + +module DrawerNavigationProp = (M: { + type params; + type options; + }) => { + include NavigationScreenProp(M); + + type t = navigation; + + [@bs.send] external openDrawer: t => unit = "openDrawer"; + [@bs.send] external closeDrawer: t => unit = "closeDrawer"; + [@bs.send] external toggleDrawer: t => unit = "toggleDrawer"; +}; + +module Make = (M: {type params;}) => { + module Navigation = + DrawerNavigationProp({ + include M; + type nonrec options = options; + }); + + type animatedNode = ReactNative.Animated.Value.t; + + type scene = { + . + "route": route(M.params), + "index": int, + "focused": bool, + "tintColor": option(string), + }; + + class type contentOptions = { + pub items: array(route(M.params)); + pub activeItemKey: option(Js.nullable(string)); + pub activeTintColor: option(string); + pub activeBackgroundColor: option(string); + pub inactiveTintColor: option(string); + pub inactiveBackgroundColor: option(string); + pub itemsContainerStyle: option(ReactNative.Style.t); + pub itemStyle: option(ReactNative.Style.t); + pub labelStyle: option(ReactNative.Style.t); + pub activeLabelStyle: option(ReactNative.Style.t); + pub inactiveLabelStyle: option(ReactNative.Style.t); + pub iconContainerStyle: option(ReactNative.Style.t); + }; + + class type virtual drawerNavigationItemsProps = { + as 'self; + constraint 'self = #contentOptions; + pub drawerPosition: string; + pub getLabel: scene => React.element; + pub renderIcon: scene => React.element; + pub onItemPress: scene => unit; + }; + + class type virtual contentComponentProps = { + as 'self; + constraint 'self = #drawerNavigationItemsProps; + pub navigation: navigation; + pub drawerOpenProgress: animatedNode; + }; + + [@bs.obj] + external options: + ( + ~title: string=?, + ~drawerLabel: scene => React.element=?, + ~drawerIcon: scene => React.element=?, + ~drawerLockMode: [@bs.string] [ + | `unlocked + | [@bs.as "locked-closed"] `lockedClosed + | [@bs.as "locked-open"] `lockedOpen + ] + =?, + unit + ) => + options = + ""; + + type optionsProps = + { + . + "navigation": navigation, + "route": route(M.params), + } => + options; + + type navigatorProps; + + type screenProps; + + [@bs.module "@react-navigation/drawer"] + external make: + unit => + { + . + "Navigator": navigatorProps => React.element, + "Screen": screenProps => React.element, + } = + "createDrawerNavigator"; + + let stack = make(); + + module Screen = { + [@bs.obj] + external makeProps: + ( + ~name: string, + ~options: optionsProps=?, + ~initialParams: M.params=?, + ~component: React.component({. "navigation": navigation}), + unit + ) => + screenProps = + ""; + let make = stack##"Screen"; + }; + + module Navigator = { + [@bs.obj] + external makeProps: + ( + ~initialRouteName: string=?, + ~screenOptions: optionsProps=?, + ~children: React.element, + ~backBehavior: [@bs.string] [ + | `initialRoute + | `order + | `history + | `none + ] + =?, + //DrawerNavigationConfig + ~drawerBackgroundColor: string=?, + ~drawerPosition: [@bs.string] [ | `left | `right]=?, + ~drawerType: [@bs.string] [ | `front | `back | `slide]=?, + /* + ~drawerWidth: [@bs.unwrap] [ + | `Static(float) + | `Dynamic(unit => float) + ] + + */ + ~drawerWidth: unit => float, + ~edgeWidth: float=?, + ~hideStatusBar: bool=?, + ~keyboardDismissMode: [@bs.string] [ + | [@bs.as "on-drag"] `onDrag + | `none + ] + =?, + ~minSwipeDistance: float=?, + ~overlayColor: string=?, + ~statusBarAnimation: [@bs.string] [ | `slide | `none | `fade]=?, + //TODO: ~gestureHandlerProps: React.ComponentProps; + ~_lazy: bool=?, + ~unmountInactiveRoutes: bool=?, + ~contentComponent: React.component(Js.t(contentComponentProps))=?, + ~contentOptions: Js.t(contentOptions)=?, + ~sceneContainerStyle: ReactNative.Style.t=?, + ~style: ReactNative.Style.t=?, + unit + ) => + navigatorProps = + ""; + + let make = stack##"Navigator"; + }; +}; diff --git a/src/Example.bs.js b/src/Example.bs.js new file mode 100644 index 00000000..7177d145 --- /dev/null +++ b/src/Example.bs.js @@ -0,0 +1,131 @@ +'use strict'; + +var React = require("react"); +var Caml_option = require("bs-platform/lib/js/caml_option.js"); +var ReactNative = require("react-native"); +var Stack$ReactNavigation = require("./Stack.bs.js"); +var Native = require("@react-navigation/native"); + +function Example$HomeScreen(Props) { + Props.navigation; + return React.createElement(ReactNative.Text, { + children: "Hello Reasonable Person!" + }); +} + +var HomeScreen = { + make: Example$HomeScreen +}; + +function Example$ModalScreen(Props) { + Props.navigation; + return React.createElement(ReactNative.Text, { + children: "Hello From Modal" + }); +} + +var ModalScreen = { + make: Example$ModalScreen +}; + +var include = Stack$ReactNavigation.Make({ }); + +var $$Screen = include.Screen; + +var $$Navigator = include.Navigator; + +function Example$MainStackScreen(Props) { + Props.navigation; + return React.createElement($$Navigator.make, { + children: React.createElement($$Screen.make, { + name: "Home", + options: (function (props) { + var match = props.route.params; + return { + title: match !== undefined ? Caml_option.valFromOption(match).name : "Reason", + headerLeft: (function (param) { + return React.createElement(ReactNative.Button, { + color: "#fff", + onPress: (function (param) { + props.navigation.navigate("MyModal"); + return /* () */0; + }), + title: "Info" + }); + }) + }; + }), + component: Example$HomeScreen + }) + }); +} + +var MainStackScreen_Navigation = include.Navigation; + +var MainStackScreen_HeaderTitle = include.HeaderTitle; + +var MainStackScreen_Header = include.Header; + +var MainStackScreen_TransitionSpec = include.TransitionSpec; + +var MainStackScreen_stack = include.stack; + +var MainStackScreen = { + Navigation: MainStackScreen_Navigation, + HeaderTitle: MainStackScreen_HeaderTitle, + Header: MainStackScreen_Header, + TransitionSpec: MainStackScreen_TransitionSpec, + stack: MainStackScreen_stack, + Screen: $$Screen, + Navigator: $$Navigator, + make: Example$MainStackScreen +}; + +var include$1 = Stack$ReactNavigation.Make({ }); + +var $$Screen$1 = include$1.Screen; + +var $$Navigator$1 = include$1.Navigator; + +function make(param) { + return React.createElement(Native.NavigationNativeContainer, { + children: React.createElement($$Navigator$1.make, { + mode: "modal", + headerMode: "none", + children: null + }, React.createElement($$Screen$1.make, { + name: "Main", + component: Example$MainStackScreen + }), React.createElement($$Screen$1.make, { + name: "MyModal", + component: Example$ModalScreen + })) + }); +} + +var RootStackScreen_Navigation = include$1.Navigation; + +var RootStackScreen_HeaderTitle = include$1.HeaderTitle; + +var RootStackScreen_Header = include$1.Header; + +var RootStackScreen_TransitionSpec = include$1.TransitionSpec; + +var RootStackScreen_stack = include$1.stack; + +var RootStackScreen = { + Navigation: RootStackScreen_Navigation, + HeaderTitle: RootStackScreen_HeaderTitle, + Header: RootStackScreen_Header, + TransitionSpec: RootStackScreen_TransitionSpec, + stack: RootStackScreen_stack, + Screen: $$Screen$1, + Navigator: $$Navigator$1, + make: make +}; + +exports.HomeScreen = HomeScreen; +exports.ModalScreen = ModalScreen; +exports.MainStackScreen = MainStackScreen; +exports.RootStackScreen = RootStackScreen; +/* include Not a pure module */ diff --git a/src/Example.re b/src/Example.re new file mode 100644 index 00000000..c2d88a1c --- /dev/null +++ b/src/Example.re @@ -0,0 +1,60 @@ +module HomeScreen = { + open ReactNative; + [@react.component] + let make = (~navigation, ~route) => + {j|Hello Reasonable Person!|j}->React.string ; +}; + +module ModalScreen = { + open ReactNative; + [@react.component] + let make = (~navigation, ~route) => + {j|Hello From Modal|j}->React.string ; +}; + +module MainStackScreen = { + open ReactNative; + include Stack.Make({ + type params = {. "name": string}; + }); + [@react.component] + let make = (~navigation, ~route) => + + + options( + ~headerLeft= + _ => +