Skip to content

Commit 8b2674f

Browse files
committed
feat(react): Tabs, TabStrip, TabStripItem, BottomNavigation, and TabContentItem for NativeScript 8
1 parent 9fca9a0 commit 8b2674f

File tree

6 files changed

+528
-0
lines changed

6 files changed

+528
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"ng-packagr": "~11.2.0",
8181
"prettier": "^2.2.1",
8282
"prompt": "^1.1.0",
83+
"react-nativescript": "^3.0.0-beta.1",
8384
"rimraf": "^3.0.2",
8485
"rxjs": "^6.6.6",
8586
"ts-patch": "^1.3.2",

packages/bottom-navigation/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,63 @@ Vue.use(BottomNavigation);
214214
</MDBottomNavigation>
215215
```
216216

217+
##
218+
219+
### NativeScript + React
220+
221+
First, register the component before any of your React NativeScript app renders. A good place to put this code is in your entrypoint file (which may be called `src/app.ts` or similar), before the `ReactNativeScript.start` function is called. Here's how to install it:
222+
223+
```ts
224+
import { registerBottomNavigation } from '@nativescript-community/ui-material-bottom-navigation/react';
225+
226+
registerBottomNavigation();
227+
```
228+
229+
When available (I've not implemented it at the time of writing, but intend to in time), it would be best to use this component via the `bottomNavigationNavigatorFactory()` API exported by [React NativeScript Navigation](https://github.com/shirakaba/react-nativescript-navigation/tree/master/react-nativescript-navigation), but here's how to use it directly:
230+
231+
```tsx
232+
import * as React from "react";
233+
234+
function ExampleTabs(){
235+
return (
236+
<bottomNavigation selectedIndex={1}>
237+
{/* The bottomTab UI is created via tabStrip (the container) and tabStripItem (for each tab) */}
238+
<tabStrip>
239+
<tabStripItem>
240+
<label>Home</label>
241+
<image src="font://&#xf015;" className="fas"/>
242+
</tabStripItem>
243+
<tabStripItem className="special">
244+
<label>Account</label>
245+
<image src="font://&#xf007;" className="fas"/>
246+
</tabStripItem>
247+
<tabStripItem className="special">
248+
<label>Search</label>
249+
<image src="font://&#xf00e;" className="fas"/>
250+
</tabStripItem>
251+
</tabStrip>
252+
253+
{/* The number of tabContentItem components should corespond to the number of TabStripItem components */}
254+
<tabContentItem>
255+
<gridLayout>
256+
<label className="h2 text-center">Home Page</label>
257+
</gridLayout>
258+
</tabContentItem>
259+
<tabContentItem>
260+
<gridLayout>
261+
<label className="h2 text-center">Account Page</label>
262+
</gridLayout>
263+
</tabContentItem>
264+
<tabContentItem>
265+
<gridLayout>
266+
<label className="h2 text-center">Search Page</label>
267+
</gridLayout>
268+
</tabContentItem>
269+
</bottomNavigation>
270+
);
271+
}
272+
```
273+
217274
## API
218275

219276
### Attributes

packages/tabs/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,63 @@ Vue.use(TabsPlugin);
214214
</MDTabs>
215215
```
216216

217+
##
218+
219+
### NativeScript + React
220+
221+
First, register the component before any of your React NativeScript app renders. A good place to put this code is in your entrypoint file (which may be called `src/app.ts` or similar), before the `ReactNativeScript.start` function is called. Here's how to install it:
222+
223+
```ts
224+
import { registerTabs } from '@nativescript-community/ui-material-tabs/react';
225+
226+
registerTabs();
227+
```
228+
229+
Normally it would be best to use this component via the `tabNavigatorFactory()` API exported by [React NativeScript Navigation](https://github.com/shirakaba/react-nativescript-navigation/tree/master/react-nativescript-navigation), but here's how to use it directly:
230+
231+
```tsx
232+
import * as React from "react";
233+
234+
function ExampleTabs(){
235+
return (
236+
<tabs selectedIndex={1}>
237+
{/* The bottomTab UI is created via tabStrip (the container) and tabStripItem (for each tab) */}
238+
<tabStrip>
239+
<tabStripItem>
240+
<label>Home</label>
241+
<image src="font://&#xf015;" className="fas"/>
242+
</tabStripItem>
243+
<tabStripItem className="special">
244+
<label>Account</label>
245+
<image src="font://&#xf007;" className="fas"/>
246+
</tabStripItem>
247+
<tabStripItem className="special">
248+
<label>Search</label>
249+
<image src="font://&#xf00e;" className="fas"/>
250+
</tabStripItem>
251+
</tabStrip>
252+
253+
{/* The number of tabContentItem components should corespond to the number of TabStripItem components */}
254+
<tabContentItem>
255+
<gridLayout>
256+
<label className="h2 text-center">Home Page</label>
257+
</gridLayout>
258+
</tabContentItem>
259+
<tabContentItem>
260+
<gridLayout>
261+
<label className="h2 text-center">Account Page</label>
262+
</gridLayout>
263+
</tabContentItem>
264+
<tabContentItem>
265+
<gridLayout>
266+
<label className="h2 text-center">Search Page</label>
267+
</gridLayout>
268+
</tabContentItem>
269+
</tabs>
270+
);
271+
}
272+
```
273+
217274
## API
218275

219276
### Attributes

src/bottom-navigation/react/index.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { NSVElement, NativeScriptProps, registerElement } from 'react-nativescript';
2+
import { warn } from 'react-nativescript/dist/shared/logger';
3+
import { BottomNavigation, SelectedIndexChangedEventData, TabContentItem, TabStrip } from '../';
4+
import { TabNavigationBaseAttributes } from '@nativescript-community/ui-material-core/tab-navigation-base/tab-navigation-base/react';
5+
6+
// Global compile-time constant (for some reason not exported by RNS itself)
7+
// eslint-disable-next-line no-var
8+
declare var __DEV__: boolean;
9+
10+
// ui/bottom-navigation/bottom-navigation.d.ts
11+
export type BottomNavigationAttributes = TabNavigationBaseAttributes & {
12+
android?: any;
13+
ios?: any;
14+
items?: TabContentItem[];
15+
onSelectedIndexChanged?: (args: SelectedIndexChangedEventData) => void;
16+
selectedIndex?: number;
17+
tabStrip?: TabStrip;
18+
};
19+
20+
declare global {
21+
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword
22+
module JSX {
23+
interface IntrinsicElements {
24+
bottomNavigation: NativeScriptProps<BottomNavigationAttributes, BottomNavigation>;
25+
}
26+
interface ElementChildrenAttribute {
27+
children: {};
28+
}
29+
}
30+
}
31+
32+
let installed: boolean = false;
33+
34+
export function registerBottomNavigation(): void {
35+
if (installed) {
36+
return;
37+
}
38+
registerElement(
39+
'bottomNavigation',
40+
// @ts-ignore I can assure that this really does extend ViewBase
41+
() => BottomNavigation,
42+
{
43+
// TODO: share the same NodeOps for both BottomNavigation and Tabs; they're identical as they both extend TabNavigationBase.
44+
nodeOps: {
45+
insert(child: NSVElement, parent: NSVElement<BottomNavigation>, atIndex?: number): void {
46+
const bottomNavigation = parent.nativeView;
47+
48+
if (child.nodeRole === 'tabStrip') {
49+
if (child.nativeView instanceof TabStrip) {
50+
bottomNavigation.tabStrip = child.nativeView;
51+
} else {
52+
if (__DEV__) {
53+
warn(`Unable to add child "${child.nativeView.constructor.name}" as the tabStrip of <bottomNavigation> as it is not an instance of TabStrip.`);
54+
}
55+
}
56+
} else if (child.nodeRole === 'items') {
57+
if (child.nativeView instanceof TabContentItem === false) {
58+
if (__DEV__) {
59+
warn(`Unable to add child "${child.nativeView.constructor.name}" to the items of <bottomNavigation> as it is not an instance of TabContentItem.`);
60+
}
61+
return;
62+
}
63+
64+
const items = bottomNavigation.items || []; // Annoyingly, it's the consumer's responsibility to ensure there's an array there!
65+
66+
if (typeof atIndex === 'undefined' || atIndex === items.length) {
67+
bottomNavigation.items = items.concat(child.nativeView as TabContentItem);
68+
} else {
69+
bottomNavigation.items = items.slice().splice(atIndex, 0, child.nativeView as TabContentItem);
70+
}
71+
} else if (child.nodeRole === 'item') {
72+
if (__DEV__) {
73+
warn(`Unable to add child "${child.nativeView.constructor.name}" to <bottomNavigation> as it had the nodeRole "item"; please correct it to "items".`);
74+
}
75+
} else {
76+
if (__DEV__) {
77+
warn(
78+
`Unable to add child "${child.nativeView.constructor.name}" to <bottomNavigation> as it does not have a nodeRole specified; ` +
79+
'please set a nodeRole of "tabStrip", or "items".'
80+
);
81+
}
82+
}
83+
},
84+
remove(child: NSVElement, parent: NSVElement<BottomNavigation>): void {
85+
const tabs = parent.nativeView;
86+
87+
if (child.nodeRole === 'tabStrip') {
88+
tabs.tabStrip = null; // Anything falsy should work.
89+
} else if (child.nodeRole === 'items') {
90+
tabs.items = (tabs.items || []).filter((i) => i !== child.nativeView);
91+
} else if (child.nodeRole === 'item') {
92+
if (__DEV__) {
93+
warn(`Unable to remove child "${child.nativeView.constructor.name}" from <bottomNavigation> as it had the nodeRole "item"; please correct it to "items".`);
94+
}
95+
} else {
96+
if (__DEV__) {
97+
warn(
98+
`Unable to remove child "${child.nativeView.constructor.name}" from <bottomNavigation> as it does not have a nodeRole specified; ` +
99+
'please set a nodeRole of "tabStrip", or "items"'
100+
);
101+
}
102+
}
103+
}
104+
}
105+
}
106+
);
107+
108+
installed = true;
109+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { PropertyChangeData } from '@nativescript/core';
2+
import { ViewAttributes } from 'react-nativescript';
3+
import { SelectedIndexChangedEventData } from '..';
4+
import { TabContentItem } from '../../tab-content-item';
5+
import { TabStrip } from '../../tab-strip';
6+
7+
// ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts
8+
export type TabNavigationBaseAttributes = ViewAttributes & {
9+
android?: any;
10+
ios?: any;
11+
items?: string | TabContentItem[];
12+
onItemsChange?: (args: PropertyChangeData) => void;
13+
onSelectedIndexChange?: (args: PropertyChangeData) => void;
14+
onSelectedIndexChanged?: (args: SelectedIndexChangedEventData) => void;
15+
onTabStripChange?: (args: PropertyChangeData) => void;
16+
selectedIndex?: string | number;
17+
tabStrip?: string | TabStrip;
18+
};

0 commit comments

Comments
 (0)