Skip to content

Commit

Permalink
feat(ui): tab-view component (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
artyorsh authored Jan 28, 2019
1 parent 5c9b5d4 commit c981213
Show file tree
Hide file tree
Showing 16 changed files with 1,259 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/framework/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,43 @@ import {
CheckBox as CheckBoxComponent,
Props as CheckBoxProps,
} from './checkbox/checkbox.component';
import {
Tab as TabComponent,
Props as TabProps,
} from './tab/tab.component';
import {
TabBar as TabBarComponent,
Props as TabBarProps,
} from './tab/tabBar.component';
import {
ViewPager,
Props as ViewPagerProps,
} from './viewPager/viewPager.component';
import {
TabView,
Props as TabViewProps,
} from './tab/tabView.component';

const Radio = styled<RadioComponent, RadioProps>(RadioComponent);
const RadioGroup = styled<RadioGroupComponent, RadioGroupProps>(RadioGroupComponent);
const CheckBox = styled<CheckBoxComponent, CheckBoxProps>(CheckBoxComponent);
const Tab = styled<TabComponent, TabProps>(TabComponent);
const TabBar = styled<TabBarComponent, TabBarProps>(TabBarComponent);

export {
Radio,
RadioGroup,
CheckBox,
Tab,
TabBar,
ViewPager,
TabView,
RadioProps,
RadioGroupProps,
CheckBoxProps,
TabProps,
TabBarProps,
ViewPagerProps,
TabViewProps,
};

13 changes: 13 additions & 0 deletions src/framework/ui/service/common.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Creates array from `source` parameter if needed
*
* @param source (T | T[]) - any object which should be wrapped to an array
* @returns (T[]) - wrapped `source` if was need to wrap, `source` otherwise
*/

export function toArray<T>(source: T | T[]): T[] {
if (Array.isArray(source)) {
return source;
}
return [source];
}
6 changes: 6 additions & 0 deletions src/framework/ui/service/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {
NativeSyntheticEvent,
NativeScrollEvent,
} from 'react-native';

export type ScrollEvent = NativeSyntheticEvent<NativeScrollEvent>;
74 changes: 74 additions & 0 deletions src/framework/ui/tab/tab.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import {
View,
Image,
Text,
ViewProps,
ImageProps,
TextProps,
ImageSourcePropType,
} from 'react-native';
import {
StyledComponentProps,
StyleType,
} from '@kitten/theme';

interface TabProps {
title?: string;
icon?: ImageSourcePropType;
}

export type Props = TabProps & StyledComponentProps & ViewProps;

export class Tab extends React.Component<Props> {

private getComponentStyle = (source: StyleType): StyleType => {
return {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
icon: {},
title: {},
};
};

private createTextComponent = (style: StyleType): React.ReactElement<TextProps> => (
<Text
style={style}
key={1}>
{this.props.title}
</Text>
);

private createImageComponent = (style: StyleType): React.ReactElement<ImageProps> => (
<Image
style={style}
key={0}
source={this.props.icon}
/>
);

private createComponentChildren = (style: StyleType): React.ReactNode => {
const { icon, title } = this.props;

return [
icon ? this.createImageComponent(style.icon) : undefined,
title ? this.createTextComponent(style.title) : undefined,
];
};

public render(): React.ReactNode {
const componentStyle: StyleType = this.getComponentStyle(this.props.themedStyle);
const children = this.createComponentChildren(componentStyle);

return (
<View
{...this.props}
style={[this.props.style, componentStyle.container]}>
{children}
</View>
);
}
}
52 changes: 52 additions & 0 deletions src/framework/ui/tab/tab.spec.contig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ThemeMappingType } from 'eva/packages/common';
import { ThemeType } from '@kitten/theme';


export const mapping: ThemeMappingType = {
Tab: {
meta: {
variants: {},
states: [
'selected',
'active',
],
},
appearance: {
default: {
mapping: {
state: {
selected: {},
active: {},
'selected.active': {},
},
},
},
},
},
TabBar: {
meta: {
variants: {},
states: [],
},
appearance: {
default: {
mapping: {
barSize: 42,
indicatorSize: 4,
indicatorBorderRadius: 2,
indicatorColor: 'pink-primary',
},
},
},
},
};

export const theme: ThemeType = {
'blue-primary': '#3366FF',
'blue-dark': '#2541CC',
'gray-light': '#DDE1EB',
'gray-primary': '#A6AEBD',
'gray-dark': '#8992A3',
'gray-highlight': '#EDF0F5',
'pink-primary': '#FF3D71',
};
170 changes: 170 additions & 0 deletions src/framework/ui/tab/tab.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';
import {
View,
ScrollView,
} from 'react-native';
import {
fireEvent,
render,
} from 'react-native-testing-library';
import { ReactTestInstance } from 'react-test-renderer';
import {
styled,
StyleProvider,
StyleProviderProps,
} from '@kitten/theme';
import {
Tab as TabComponent,
Props as TabProps,
} from './tab.component';
import {
TabBar as TabBarComponent,
Props as TabBarProps,
} from './tabBar.component';
import {
TabView,
Props as TabViewProps,
ChildProps as TabViewChildProps,
} from './tabView.component';
import * as config from './tab.spec.contig';

const Tab = styled<TabComponent, TabProps>(TabComponent);
const TabBar = styled<TabBarComponent, TabBarProps>(TabBarComponent);

describe('@tab: component checks', () => {

const Mock = (props?: TabProps): React.ReactElement<StyleProviderProps> => (
<StyleProvider mapping={config.mapping} theme={config.theme} styles={{}}>
<Tab {...props} />
</StyleProvider>
);

it('* empty', () => {
const component = render(
<Mock/>,
);

expect(component).toMatchSnapshot();
});

it('* title', () => {
const component = render(
<Mock
title='title'
/>,
);

expect(component).toMatchSnapshot();
});

it('* icon', () => {
const component = render(
<Mock
icon={{ uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png' }}
/>,
);

expect(component).toMatchSnapshot();
});

});

describe('@tab-bar: component checks', () => {

const childTestId0: string = '@tab-bar/child-0';
const childTestId1: string = '@tab-bar/child-1';

const Mock = (props?: TabBarProps): React.ReactElement<StyleProviderProps> => (
<StyleProvider mapping={config.mapping} theme={config.theme} styles={{}}>
<TabBar {...props}>{props.children}</TabBar>
</StyleProvider>
);

const ChildMock = Tab;

it('* emits onSelect with correct args', () => {
const onSelect = jest.fn();

const component = render(
<Mock onSelect={onSelect}>
<ChildMock testID={childTestId0}/>
<ChildMock testID={childTestId1}/>
</Mock>,
);

const child1 = component.getByTestId(childTestId1);

fireEvent.press(child1);

expect(onSelect).toBeCalledWith(1);
});

});

describe('@tab-view: component checks', () => {

const Mock = (props?: TabViewProps): React.ReactElement<TabViewProps> => (
<StyleProvider mapping={config.mapping} theme={config.theme} styles={{}}>
<TabView {...props}>{props.children}</TabView>
</StyleProvider>
);

const ChildMock = (props?: TabViewChildProps): React.ReactElement<TabViewChildProps> => (
<Tab {...props} />
);

it('* emits onSelect with correct args', () => {
const onSelect = jest.fn();

const component = render(
<Mock contentWidth={375} onSelect={onSelect}>
<ChildMock>
<View/>
</ChildMock>
<ChildMock>
<View/>
</ChildMock>
</Mock>,
);

const scrollView: ReactTestInstance = component.getByType(ScrollView);

fireEvent(scrollView, 'momentumScrollEnd', {
nativeEvent: {
contentOffset: {
x: 375,
},
},
});

expect(onSelect).toBeCalledWith(1);
});

it('* shouldLoadComponent disables child loading', () => {
const disabledComponentIndex: number = 1;

const shouldLoadComponent = jest.fn((...args: any[]) => {
const index: number = args[0];
return index !== disabledComponentIndex;
});

const component = render(
<Mock contentWidth={375} shouldLoadComponent={shouldLoadComponent}>
<ChildMock>
<View/>
</ChildMock>
<ChildMock>
<View/>
</ChildMock>
</Mock>,
);

const scrollView: ReactTestInstance = component.getByType(ScrollView);

const unloadedChild = scrollView.props.children[disabledComponentIndex];

expect(unloadedChild.props.children).toEqual(undefined);
});

});

Loading

0 comments on commit c981213

Please sign in to comment.