Skip to content

Commit 1e7a806

Browse files
committed
Add an underlined top tab bar control.
1 parent c130ff9 commit 1e7a806

File tree

11 files changed

+1756
-0
lines changed

11 files changed

+1756
-0
lines changed

Diff for: index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export { createStateStoreManagerComponent } from "./react-native/components/crea
5454
export { createStatusPillComponent } from "./react-native/components/createStatusPillComponent";
5555
export { createTabRoutingComponent } from "./react-native/components/createTabRoutingComponent";
5656
export { createTextComponent } from "./react-native/components/createTextComponent";
57+
export { createUnderlinedTopTabBarComponent } from "./react-native/components/createUnderlinedTopTabBarComponent";
5758
export { CustomElementTableColumn } from "./react-native/types/CustomElementTableColumn";
5859
export { CustomTextTableColumn } from "./react-native/types/CustomTextTableColumn";
5960
export { EmptyRequestBody } from "./react-native/types/EmptyRequestBody";
@@ -101,6 +102,9 @@ export { TableSchema } from "./react-native/types/TableSchema";
101102
export { TableStyle } from "./react-native/types/TableStyle";
102103
export { TabRoute } from "./react-native/types/TabRoute";
103104
export { TabRouteTable } from "./react-native/types/TabRouteTable";
105+
export { UnderlinedTopTab } from "./react-native/types/UnderlinedTopTab";
106+
export { UnderlinedTopTabBarStyle } from "./react-native/types/UnderlinedTopTabBarStyle";
107+
export { UnderlinedTopTabBarStyleState } from "./react-native/types/UnderlinedTopTabBarStyleState";
104108
export { unwrapRenderedFunctionComponent } from "./react-native/utilities/unwrapRenderedFunctionComponent";
105109
export { useBackButton } from "./react-native/hooks/useBackButton";
106110
export { useEventRefresh } from "./react-native/hooks/useEventRefresh";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import * as React from "react";
2+
import { StyleSheet, Text, TextStyle, ViewStyle } from "react-native";
3+
import type { UnderlinedTopTab } from "../../types/UnderlinedTopTab";
4+
import type { UnderlinedTopTabBarStyle } from "../../types/UnderlinedTopTabBarStyle";
5+
import { Hitbox } from "../Hitbox";
6+
import { HorizontallySymmetricalSafeAreaView } from "../HorizontallySymmetricalSafeAreaView";
7+
8+
/**
9+
* Creates a new React component which can be used to render a top tab bar,
10+
* where an underline is used to distinguish active and inactive tabs.
11+
* @template TTab The type which represents the selected tab.
12+
* @param underlinedTopTabBarStyle The style to apply to the tab bar.
13+
* @param underlinedTopTabs The tabs to include in the bar.
14+
* @returns The created React component.
15+
*/
16+
export function createUnderlinedTopTabBarComponent<
17+
TTab extends string | number | null | undefined
18+
>(
19+
underlinedTopTabBarStyle: UnderlinedTopTabBarStyle,
20+
underlinedTopTabs: ReadonlyArray<UnderlinedTopTab<TTab>>
21+
): React.FunctionComponent<{ readonly tab: TTab; setTab(to: TTab): void }> {
22+
const inactiveUnderlineWidth =
23+
underlinedTopTabBarStyle.inactive.underline === null
24+
? 0
25+
: underlinedTopTabBarStyle.inactive.underline.width;
26+
27+
const hitboxBase: ViewStyle = {
28+
flexBasis: 0,
29+
flexGrow: 1,
30+
};
31+
32+
if (underlinedTopTabBarStyle.verticalPadding !== 0) {
33+
hitboxBase.paddingTop = underlinedTopTabBarStyle.verticalPadding;
34+
}
35+
36+
if (inactiveUnderlineWidth !== 0) {
37+
hitboxBase.marginBottom = -inactiveUnderlineWidth;
38+
}
39+
40+
const inactiveHitbox: ViewStyle = {
41+
...hitboxBase,
42+
};
43+
44+
const activeHitbox: ViewStyle = {
45+
...hitboxBase,
46+
backgroundColor: underlinedTopTabBarStyle.active.backgroundColor,
47+
};
48+
49+
if (underlinedTopTabBarStyle.active.underline !== null) {
50+
activeHitbox.borderBottomWidth =
51+
underlinedTopTabBarStyle.active.underline.width;
52+
activeHitbox.borderBottomColor =
53+
underlinedTopTabBarStyle.active.underline.color;
54+
}
55+
56+
const textBase: TextStyle = {
57+
fontSize: underlinedTopTabBarStyle.fontSize,
58+
lineHeight: underlinedTopTabBarStyle.fontSize * 1.4,
59+
textAlign: `center`,
60+
};
61+
62+
const horizontallySymmetricalSafeAreaView: ViewStyle = {
63+
flexDirection: `row`,
64+
backgroundColor: underlinedTopTabBarStyle.inactive.backgroundColor,
65+
height:
66+
underlinedTopTabBarStyle.verticalPadding +
67+
underlinedTopTabBarStyle.fontSize * 1.4 +
68+
underlinedTopTabBarStyle.verticalPadding,
69+
};
70+
71+
if (underlinedTopTabBarStyle.inactive.underline !== null) {
72+
horizontallySymmetricalSafeAreaView.borderBottomWidth =
73+
underlinedTopTabBarStyle.inactive.underline.width;
74+
horizontallySymmetricalSafeAreaView.borderBottomColor =
75+
underlinedTopTabBarStyle.inactive.underline.color;
76+
}
77+
78+
const styles = StyleSheet.create({
79+
horizontallySymmetricalSafeAreaView,
80+
inactiveHitbox,
81+
activeHitbox,
82+
inactiveText: {
83+
...textBase,
84+
fontFamily: underlinedTopTabBarStyle.inactive.fontFamily,
85+
color: underlinedTopTabBarStyle.inactive.color,
86+
},
87+
activeText: {
88+
...textBase,
89+
fontFamily: underlinedTopTabBarStyle.active.fontFamily,
90+
color: underlinedTopTabBarStyle.active.color,
91+
},
92+
});
93+
94+
return ({ tab, setTab }) => (
95+
<HorizontallySymmetricalSafeAreaView
96+
style={styles.horizontallySymmetricalSafeAreaView}
97+
left
98+
right
99+
>
100+
{underlinedTopTabs.map((item) => {
101+
const isActive = item.tab === tab;
102+
return (
103+
<Hitbox
104+
key={item.tab}
105+
disabled={isActive}
106+
style={isActive ? styles.activeHitbox : styles.inactiveHitbox}
107+
onPress={() => {
108+
setTab(item.tab);
109+
}}
110+
>
111+
<Text
112+
numberOfLines={1}
113+
style={isActive ? styles.activeText : styles.inactiveText}
114+
>
115+
{item.text}
116+
</Text>
117+
</Hitbox>
118+
);
119+
})}
120+
</HorizontallySymmetricalSafeAreaView>
121+
);
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# `react-native-app-helpers/createUnderlinedTopTabBarComponent`
2+
3+
Creates a new React component which can be used to render a top tab bar, where
4+
an underline is used to distinguish active and inactive tabs.
5+
6+
## Usage
7+
8+
```tsx
9+
import { createUnderlinedTopTabBarComponent } from "react-native-app-helpers";
10+
11+
type ExampleTab = `A` | `B` | `C`;
12+
13+
const ExampleTabBar = createUnderlinedTopTabBarComponent(
14+
{
15+
fontSize: 20,
16+
verticalPadding: 10,
17+
inactive: {
18+
color: `yellow`,
19+
fontFamily: `Example Inactive Font Family`,
20+
backgroundColor: `red`,
21+
underline: { width: 4, color: `blue` },
22+
},
23+
active: {
24+
color: `green`,
25+
fontFamily: `Example Active Font Family`,
26+
backgroundColor: `orange`,
27+
underline: { width: 6, color: `purple` },
28+
},
29+
},
30+
[
31+
{ tab: `A`, text: `Example Tab A Text` },
32+
{ tab: `B`, text: `Example Tab B Text` },
33+
{ tab: `C`, text: `Example Tab C Text` },
34+
]
35+
);
36+
37+
const ExampleScreen = () => {
38+
const [tab, setTab] = React.useState<ExampleTab>(`A`);
39+
40+
return (
41+
<ExampleTabBar tab={tab} setTab={setTab} />
42+
);
43+
};
44+
```

0 commit comments

Comments
 (0)