Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat add tabs components #13

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"react-native": "0.69.5",
"react-native-animatable": "^1.3.3",
"react-native-gesture-handler": "~2.5.0",
"react-native-modal": "^13.0.1",
"react-native-modal": "13.0.1",
"react-native-reanimated": "~2.9.1",
"react-native-safe-area-context": "4.3.1",
"react-native-screens": "~3.15.0",
Expand Down
43 changes: 43 additions & 0 deletions example/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {
Tabs,
TabList,
Tab,
TabPanel,
TabPanels,
Text,
SafeAreaBox,
} from 'react-native-ficus-ui';

const TabsExampleComponent = () => {
const [index, setIndex] = React.useState(0);
return (
<SafeAreaBox flex={1}>
<Tabs
colorScheme="green"
initialPage={0}
onChangeTab={setIndex}
selectedTab={index}
>
<TabList>
<Tab name="first">Tab 1</Tab>
<Tab name="second">Tab 2</Tab>
<Tab name="third">Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel linkedTo="first" bg="red.800" p="lg">
<Text color="white">Content for the first tab</Text>
</TabPanel>
<TabPanel linkedTo="second" bg="gray.600" p="lg">
<Text color="white">Content for the second tab</Text>
</TabPanel>
<TabPanel linkedTo="third" bg="yellow.400" p="lg">
<Text>Content for the third tab</Text>
</TabPanel>
</TabPanels>
</Tabs>
</SafeAreaBox>
);
};

export default TabsExampleComponent;
6 changes: 6 additions & 0 deletions example/src/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ModalComponent from './components/Modal';
import FlashListComponent from './components/FlashList';
import SafeAreaBoxComponent from './components/SafeAreaBox';
import DividerComponent from './components/Divider';
import TabsComponent from './components/Tabs';

type ExampleComponentType = {
onScreenName: string;
Expand Down Expand Up @@ -141,4 +142,9 @@ export const components: ExampleComponentType[] = [
onScreenName: 'Modal',
component: ModalComponent,
},
{
navigationPath: 'Tabs',
onScreenName: 'Tabs',
component: TabsComponent,
},
];
2 changes: 1 addition & 1 deletion example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8559,7 +8559,7 @@ react-native-gradle-plugin@^0.0.7:
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz#96602f909745239deab7b589443f14fce5da2056"
integrity sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==

react-native-modal@^13.0.1:
[email protected]:
version "13.0.1"
resolved "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-13.0.1.tgz#691f1e646abb96fa82c1788bf18a16d585da37cd"
integrity sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
"deepmerge": "4.2.2",
"react-native-animatable": "1.3.3",
"react-native-modal": "13.0.1",
"react-native-pager-view": "6.2.2",
"react-native-reanimated": "3.5.4",
"react-native-tab-view": "3.5.2",
"react-native-toast-message": "2.1.6",
"react-native-vector-icons": "10.0.0",
"validate-color": "2.2.1"
Expand Down Expand Up @@ -98,7 +101,7 @@
"engines": {
"node": ">= 18.0.0"
},
"packageManager": "^[email protected].15",
"packageManager": "[email protected].21",
"jest": {
"preset": "react-native",
"modulePaths": [
Expand Down
8 changes: 8 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ export { RadioProps, RadioGroupProps } from './radio/radio.type';
export { RadioGroup } from './radio/group.component';
export { Modal } from './modal/modal.component';
export { ModalProps } from './modal/modal.type';
export { Tab, TabList, TabPanel, TabPanels, Tabs } from './tabs/tabs.component';
export {
TabListProps,
TabPanelProps,
TabProps,
TabsProps,
TabPanelsProps,
} from './tabs/tabs.type';
157 changes: 157 additions & 0 deletions src/components/tabs/tabs.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// tabs.component.tsx
import React, { FC, ReactNode, useState } from 'react';
import { useWindowDimensions, View } from 'react-native';
import { TabView, SceneMap, TabBar, TabBarProps } from 'react-native-tab-view';
import { useTheme } from '../../theme';
import { getStyle } from './tabs.style';
import {
TabListProps,
TabPanelProps,
TabPanelsProps,
TabProps,
TabsProps,
} from './tabs.type';
import { Text } from '../text/text.component';
import { getThemeColor } from '../../theme/theme.service';

const Tab: FC<TabProps> = ({ name, children, ...rest }) => {
const { theme } = useTheme();
const styles = getStyle(theme, rest);
return <View style={styles.box}>{children as ReactNode}</View>;
};
Tab.displayName = 'Tab';

const TabPanels: FC<TabPanelsProps> = ({ children, ...rest }) => {
const { theme } = useTheme();
const styles = getStyle(theme, rest);
return <View style={styles.box}>{children}</View>;
};
TabPanels.displayName = 'TabPanels';

const TabList: FC<TabListProps> = ({ children }) => {
return <View>{children}</View>;
};
TabList.displayName = 'TabList';

const TabPanel: FC<TabPanelProps> = ({ children, ...rest }) => {
const { theme } = useTheme();
const styles = getStyle(theme, rest);
return <View style={{ ...styles.box, ...styles.tabPanel }}>{children}</View>;
};
TabPanel.displayName = 'TabPanel';

const Tabs: FC<TabsProps> = ({
children,
selectedTab,
onChangeTab,
initialPage,
colorScheme,
...rest
}) => {
const layout = useWindowDimensions();
const { theme } = useTheme();
const [index, setIndex] = useState(initialPage || 0);

const tabs = React.Children.toArray(children).find(
(child: any) => child.type?.displayName === 'TabList'
) as any;

const panels = React.Children.toArray(children).find(
(child: any) => child.type?.displayName === 'TabPanels'
) as any;

const tabsMainColor = colorScheme
? getThemeColor(theme.colors, `${colorScheme}.600`)
: null;

// Construct the routes from Tab children
const routes = React.Children.map((tabs as any)?.props.children, (child) => ({
key: child.props.name,
title: child.props.name,
renderLabelChildren: child.props.children,
otherProps: child.props,
}));

const tabSceneMap = panels?.props.children.reduce(
(scenes: any, child: any) => {
const { linkedTo, children: panelChildren, ...panelRest } = child.props;
scenes[linkedTo] = () => (
<TabPanel {...panelRest}>{panelChildren}</TabPanel>
);
return scenes;
},
{}
);

// Construct the scene map from TabPanel children
const renderScene = SceneMap(tabSceneMap);

const renderLabel: TabBarProps<any>['renderLabel'] = ({
route,
focused,
color,
}) => {
if (typeof route.renderLabelChildren === 'string') {
return (
<Text style={{ color: tabsMainColor || color }}>
{route.renderLabelChildren}
</Text>
);
}
return (
<View>
{typeof route.renderLabelChildren === 'function'
? route.renderLabelChildren({
route,
focused,
color,
})
: route.renderLabelChildren}
</View>
);
};

// Style processing
const styles = getStyle(theme, rest);
const { children: tabsChildren, ...otherTabsProps } = tabs?.props as any;
const {
children: panelsChildren,
...otherPanelsProps
} = panels?.props as any;

const panelsStyles = getStyle(theme, otherPanelsProps);
const tabBarStyle = getStyle(theme, otherTabsProps);

return (
<TabView
{...rest}
navigationState={{
index: selectedTab !== undefined ? selectedTab : index,
routes,
}}
initialLayout={{ width: layout.width }}
sceneContainerStyle={panelsStyles.box}
renderScene={renderScene}
onIndexChange={(ind) => {
onChangeTab(ind);
setIndex(ind);
}}
renderTabBar={(props) => {
return (
<TabBar
renderLabel={renderLabel}
style={tabBarStyle.box}
{...(colorScheme
? { indicatorStyle: { backgroundColor: tabsMainColor } }
: {})}
{...props}
{...otherTabsProps}
/>
);
}}
style={styles.box}
/>
);
};

export { Tabs, TabList, Tab, TabPanel, TabPanels };
77 changes: 77 additions & 0 deletions src/components/tabs/tabs.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { StyleSheet } from 'react-native';

import {
createShadowStyles,
createPositionStyle,
createSpacingStyles,
createBorderWidthStyles,
createBorderColorStyles,
createBorderRadiusStyles,
getThemeColor,
} from '../../theme/theme.service';
import type { ThemeType } from '../../theme/type';
import { BoxProps } from 'components/box/box.type';

// Tab view component from react-native-tab-view has a bug with style prop i have to use this workaround to fix it
const removeUndefinedProps = (obj: any) => {
Object.keys(obj).forEach((key) => {
if (obj[key] === undefined) {
delete obj[key];
}
});
};

/**
* computed style
*
* @param theme
* @param props
*/
export const getStyle = (theme: ThemeType, props: BoxProps) => {
const computedStyle: any = {};

computedStyle.box = {
flexDirection: props.direction ? props.direction : props.flexDirection,
flexWrap: props.wrap ? props.wrap : props.flexWrap,
alignItems: props.align ? props.align : props.alignItems,
justifyContent: props.justify ? props.justify : props.justifyContent,
flexBasis: props.basis ? props.basis : props.flexBasis,
flexGrow: props.grow ? props.grow : props.flexGrow,
flexShrink: props.shrink ? props.shrink : props.flexShrink,
height: props.h,
width: props.w,
minWidth: props.minW,
minHeight: props.minH,
alignSelf: props.alignSelf,
maxWidth: props.maxW,
maxHeight: props.maxH,
opacity: props.opacity,
overflow: props.overflow,
zIndex: props.zIndex,
borderStyle: props.borderStyle,
backgroundColor: getThemeColor(theme.colors, props.bg as string),
flex: props.flex,
...createPositionStyle(props),
...createShadowStyles(props, theme),
...createBorderWidthStyles(props),
...createSpacingStyles(props, theme.spacing),
...createBorderColorStyles(props, theme.colors),
...createBorderRadiusStyles(props, theme.borderRadius),
};

computedStyle.tabPanel = {
flex: 1,
};

removeUndefinedProps(computedStyle.box);

// merging custom style props to computed style
if (props.style) {
computedStyle.box = {
...computedStyle.box,
...(props as any).style,
};
}

return StyleSheet.create(computedStyle);
};
Loading