Skip to content

Commit 61649db

Browse files
shubhamzanwara7ul
andauthored
improv: add tabs support (#149)
* wip: implementing RNTab with position and tabs * tabs: adding the config for tab element * demo: using tab widget in demo * Fixes basic tab rendering * Adds tab item proxy * packages: using the master release of nodegui * components: adding mechanism to notify parent of prop update * demo: changing the tab text on button click * Simplified implementation of tabitem updates * tabs: implementing removeTab and insertChild Co-authored-by: Atul R <atulanand94@gmail.com>
1 parent e424a6e commit 61649db

File tree

7 files changed

+269
-37
lines changed

7 files changed

+269
-37
lines changed

src/components/Tab/RNTab.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {
2+
QTabWidget,
3+
QTabWidgetSignals,
4+
TabPosition,
5+
QIcon,
6+
NodeWidget
7+
} from "@nodegui/nodegui";
8+
import { ViewProps, setViewProps } from "../View/RNView";
9+
import { RNComponent } from "../config";
10+
import { RNTabItem, setTabItemProps } from "../TabItem/RNTabItem";
11+
12+
export interface TabProps extends ViewProps<QTabWidgetSignals> {
13+
tabPosition?: TabPosition;
14+
}
15+
16+
/**
17+
* @ignore
18+
*/
19+
export const setTabProps = (
20+
widget: RNTab,
21+
newProps: TabProps,
22+
oldProps: TabProps
23+
) => {
24+
const setter: TabProps = {
25+
set tabPosition(value: TabPosition) {
26+
widget.setTabPosition(value);
27+
}
28+
};
29+
Object.assign(setter, newProps);
30+
setViewProps(widget, newProps, oldProps);
31+
};
32+
33+
/**
34+
* @ignore
35+
*/
36+
export class RNTab extends QTabWidget implements RNComponent {
37+
setProps(newProps: TabProps, oldProps: TabProps): void {
38+
setTabProps(this, newProps, oldProps);
39+
}
40+
41+
appendInitialChild(tabItem: RNTabItem): void {
42+
if (!(tabItem instanceof RNTabItem)) {
43+
throw new Error("Children of tab should be of type TabItem");
44+
}
45+
46+
if (tabItem.actualTabWidget) {
47+
this.addTab(tabItem.actualTabWidget, new QIcon(), "");
48+
tabItem.parentTab = this;
49+
setTabItemProps(tabItem, this, tabItem.initialProps, {});
50+
}
51+
}
52+
appendChild(child: RNTabItem): void {
53+
this.appendInitialChild(child);
54+
}
55+
insertBefore(child: RNTabItem, beforeChild: RNTabItem): void {
56+
if (!(child instanceof RNTabItem)) {
57+
throw new Error("Children of tab should be of type TabItem");
58+
}
59+
// uncomment below code after new release of nodegui containing insertTab
60+
// const index = this.indexOf(beforeChild.actualTabWidget as NodeWidget<any>);
61+
// this.insertTab(index, child.actualTabWidget, new QIcon(), "");
62+
// child.parentTab = this;
63+
// setTabItemProps(child, this, child.initialProps, {});
64+
}
65+
removeChild(child: RNTabItem): void {
66+
const childIndex = this.indexOf(child.actualTabWidget as NodeWidget<any>);
67+
this.removeTab(childIndex);
68+
}
69+
static tagName = "tabwidget";
70+
}

src/components/Tab/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Fiber } from "react-reconciler";
2+
import { registerComponent, ComponentConfig } from "../config";
3+
import { RNTab, TabProps } from "./RNTab";
4+
import { AppContainer } from "../../reconciler";
5+
class TabsConfig extends ComponentConfig {
6+
tagName = RNTab.tagName;
7+
shouldSetTextContent(nextProps: TabProps): boolean {
8+
return false;
9+
}
10+
createInstance(
11+
newProps: TabProps,
12+
rootInstance: AppContainer,
13+
context: any,
14+
workInProgress: Fiber
15+
): RNTab {
16+
const widget = new RNTab();
17+
widget.setProps(newProps, {});
18+
return widget;
19+
}
20+
commitMount(
21+
instance: RNTab,
22+
newProps: TabProps,
23+
internalInstanceHandle: any
24+
): void {
25+
if (newProps.visible !== false) {
26+
instance.show();
27+
}
28+
}
29+
finalizeInitialChildren(
30+
instance: RNTab,
31+
newProps: TabProps,
32+
rootContainerInstance: AppContainer,
33+
context: any
34+
): boolean {
35+
return true;
36+
}
37+
commitUpdate(
38+
instance: RNTab,
39+
updatePayload: any,
40+
oldProps: TabProps,
41+
newProps: TabProps,
42+
finishedWork: Fiber
43+
): void {
44+
instance.setProps(newProps, oldProps);
45+
}
46+
}
47+
48+
export const Tabs = registerComponent<TabProps>(new TabsConfig());

src/components/TabItem/RNTabItem.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { NodeWidget, QIcon, Component } from "@nodegui/nodegui";
2+
import { RNComponent, RNProps } from "../config";
3+
import { RNTab } from "../Tab/RNTab";
4+
5+
export interface TabItemProps {
6+
title?: string;
7+
icon?: QIcon;
8+
}
9+
10+
/**
11+
* @ignore
12+
*/
13+
export const setTabItemProps = (
14+
tabItem: RNTabItem,
15+
parentTab: RNTab,
16+
newProps: TabItemProps,
17+
oldProps: TabItemProps
18+
) => {
19+
if (!tabItem.actualTabWidget) {
20+
return;
21+
}
22+
const tabIndex = parentTab.indexOf(tabItem.actualTabWidget);
23+
if (tabIndex < 0) {
24+
console.error("TabItem is not part of the parent tab it references to");
25+
return;
26+
}
27+
28+
const setter: TabItemProps = {
29+
set title(text: string) {
30+
parentTab.setTabText(tabIndex, text);
31+
},
32+
set icon(qicon: QIcon) {
33+
parentTab.setTabIcon(tabIndex, qicon);
34+
}
35+
};
36+
Object.assign(setter, newProps);
37+
};
38+
39+
/**
40+
* @ignore
41+
*/
42+
export class RNTabItem extends Component implements RNComponent {
43+
native: any;
44+
actualTabWidget?: NodeWidget<any>;
45+
initialProps: TabItemProps = {};
46+
parentTab?: RNTab;
47+
48+
setProps(newProps: TabItemProps, oldProps: TabItemProps): void {
49+
if (this.parentTab) {
50+
setTabItemProps(this, this.parentTab, newProps, oldProps);
51+
} else {
52+
this.initialProps = newProps;
53+
}
54+
}
55+
appendInitialChild(child: NodeWidget<any>): void {
56+
if (this.actualTabWidget) {
57+
throw new Error("Tab Item can have only one child");
58+
}
59+
this.actualTabWidget = child;
60+
}
61+
appendChild(child: NodeWidget<any>): void {
62+
this.appendInitialChild(child);
63+
}
64+
insertBefore(child: NodeWidget<any>, beforeChild: NodeWidget<any>): void {
65+
this.appendInitialChild(child);
66+
}
67+
removeChild(child: NodeWidget<any>): void {
68+
child.close();
69+
delete this.actualTabWidget;
70+
}
71+
static tagName: string = "tabitem";
72+
}

src/components/TabItem/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Fiber } from "react-reconciler";
2+
import { registerComponent, ComponentConfig } from "../config";
3+
import { RNTabItem, TabItemProps } from "./RNTabItem";
4+
import { AppContainer } from "../../reconciler";
5+
6+
class TabItemConfig extends ComponentConfig {
7+
tagName = RNTabItem.tagName;
8+
shouldSetTextContent(nextProps: TabItemProps): boolean {
9+
return false;
10+
}
11+
createInstance(
12+
newProps: TabItemProps,
13+
rootInstance: AppContainer,
14+
context: any,
15+
workInProgress: Fiber
16+
): RNTabItem {
17+
const item = new RNTabItem();
18+
item.setProps(newProps, {});
19+
return item;
20+
}
21+
finalizeInitialChildren(
22+
instance: RNTabItem,
23+
newProps: TabItemProps,
24+
rootContainerInstance: AppContainer,
25+
context: any
26+
): boolean {
27+
return false;
28+
}
29+
commitUpdate(
30+
instance: RNTabItem,
31+
updatePayload: any,
32+
oldProps: TabItemProps,
33+
newProps: TabItemProps,
34+
finishedWork: Fiber
35+
): void {
36+
instance.setProps(newProps, oldProps);
37+
}
38+
}
39+
40+
export const TabItem = registerComponent<TabItemProps>(new TabItemConfig());

src/components/config.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import { NodeWidget } from "@nodegui/nodegui";
1+
import { NodeWidget, Component } from "@nodegui/nodegui";
22
import { Fiber } from "react-reconciler";
33
import { AppContainer } from "../reconciler";
44

55
type UpdatePayload = any;
66

77
export interface RNProps {}
8-
export abstract class RNWidget extends NodeWidget<any> implements RNComponent {
8+
9+
export abstract class RNComponent {
910
static tagName: string;
1011
abstract setProps(newProps: RNProps, oldProps: RNProps): void;
11-
abstract appendInitialChild(child: NodeWidget<any>): void;
12-
abstract appendChild(child: NodeWidget<any>): void;
13-
abstract insertBefore(
14-
child: NodeWidget<any>,
15-
beforeChild: NodeWidget<any>
16-
): void;
17-
abstract removeChild(child: NodeWidget<any>): void;
12+
abstract appendInitialChild(child: Component): void;
13+
abstract appendChild(child: Component): void;
14+
abstract insertBefore(child: Component, beforeChild: Component): void;
15+
abstract removeChild(child: Component): void;
1816
}
19-
export abstract class RNComponent {
17+
export abstract class RNWidget extends NodeWidget<any> implements RNComponent {
2018
static tagName: string;
2119
abstract setProps(newProps: RNProps, oldProps: RNProps): void;
2220
abstract appendInitialChild(child: NodeWidget<any>): void;
@@ -35,9 +33,9 @@ export abstract class ComponentConfig {
3533
abstract shouldSetTextContent(nextProps: RNProps): boolean;
3634
abstract createInstance(
3735
newProps: RNProps,
38-
rootInstance: AppContainer,
39-
context: any,
40-
workInProgress: Fiber
36+
rootInstance?: AppContainer,
37+
context?: any,
38+
workInProgress?: Fiber
4139
): RNComponent;
4240
finalizeInitialChildren(
4341
instance: RNComponent,

src/demo.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import React from "react";
1+
import React, {useState} from "react";
22
import {
33
Renderer,
44
Button,
55
Window,
6+
TabItem,
67
View,
78
AnimatedImage,
89
ComboBox,
9-
Text
10+
Text,
11+
Tabs
1012
} from "./index";
11-
import { QIcon, QVariant, QPushButtonSignals } from "@nodegui/nodegui";
13+
import { QIcon, QVariant, QPushButtonSignals, QSize } from "@nodegui/nodegui";
1214
import { useEventHandler } from "./hooks";
1315

1416
const items = [
@@ -23,33 +25,33 @@ const items = [
2325
];
2426

2527
const App = () => {
28+
const [titleCount, setTitleCount] = useState(0);
2629
const handler = useEventHandler<QPushButtonSignals>(
2730
{
28-
clicked: clicked => { console.log("clicked"); }
31+
clicked: () => {
32+
console.log(titleCount);
33+
setTitleCount(titleCount + 1);
34+
}
2935
},
30-
[]
36+
[titleCount]
3137
);
38+
// TODO: need to figure out a way to add tab title and icon
3239
return (
3340
<Window>
34-
<View style={containerStyle}>
35-
<Text openExternalLinks={true}>
36-
{`<a
37-
style="color: white"
38-
href="https://react.nodegui.org/docs/guides/getting-started/">
39-
docs
40-
</a>`}
41-
</Text>
42-
<View on={{}}>
43-
<Button on={handler} style={buttonStyle} text={"Hello"} />
44-
<Button style={buttonStyle} text={"World"} />
45-
</View>
46-
<ComboBox items={items} />
47-
{/* commenting this out while I still figure out the error;
48-
<AnimatedImage
49-
style="border: 1px solid blue; flex:1;"
50-
src="/Users/atulr/Project/nodegui/nodegui/src/lib/QtGui/__tests__/assets/fine.gif"
51-
/> */}
52-
</View>
41+
<Tabs tabPosition={0}>
42+
<TabItem
43+
title={`title-${titleCount}`}
44+
icon={
45+
new QIcon(
46+
"/Users/atulr/Project/nodegui/nodegui/src/lib/QtGui/__tests__/assets/nodegui.png"
47+
)
48+
}
49+
>
50+
<View>
51+
<Button on={handler} style={buttonStyle} text={"change title"} />
52+
</View>
53+
</TabItem>
54+
</Tabs>
5355
</Window>
5456
);
5557
};

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ export { Dial } from "./components/Dial";
1515
export { SpinBox } from "./components/SpinBox";
1616
export { ScrollArea } from "./components/ScrollArea";
1717
export { ComboBox } from "./components/ComboBox";
18+
export { Tabs } from "./components/Tab";
19+
export { TabItem } from "./components/TabItem";
1820
export { useEventHandler } from "./hooks";
1921
export { hot, appProxy } from "./development/hot-reload";

0 commit comments

Comments
 (0)