Skip to content

Commit

Permalink
feat(ui): top navigation bar component
Browse files Browse the repository at this point in the history
  • Loading branch information
32penkin authored and artyorsh committed Feb 20, 2019
1 parent 8696132 commit 3421311
Show file tree
Hide file tree
Showing 8 changed files with 1,089 additions and 1 deletion.
16 changes: 15 additions & 1 deletion src/framework/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ import {
TabView,
Props as TabViewProps,
} from './tab/tabView.component';
import {
TopNavigationBar as TopNavigationBarComponent,
Props as TopNavigationBarProps,
} from './topNavigationBar/topNavigationBar.component';
import {
TopNavigationBarAction as TopNavigationBarActionComponent,
Props as TopNavigationBarActionProps,
} from './topNavigationBar/topNavigationBarAction.component';
import {
Modal as ModalComponent,
Props as ModalProps,
Expand All @@ -47,6 +55,9 @@ const Toggle = styled<ToggleComponent, ToggleProps>(ToggleComponent);
const CheckBox = styled<CheckBoxComponent, CheckBoxProps>(CheckBoxComponent);
const Tab = styled<TabComponent, TabProps>(TabComponent);
const TabBar = styled<TabBarComponent, TabBarProps>(TabBarComponent);
const TopNavigationBar = styled<TopNavigationBarComponent, TopNavigationBarProps>(TopNavigationBarComponent);
const TopNavigationBarAction =
styled<TopNavigationBarActionComponent, TopNavigationBarActionProps>(TopNavigationBarActionComponent);
const Modal = styled<ModalComponent, ModalProps>(ModalComponent);

export {
Expand All @@ -68,7 +79,10 @@ export {
TabBarProps,
ViewPagerProps,
TabViewProps,
TopNavigationBar,
TopNavigationBarProps,
TopNavigationBarAction,
TopNavigationBarActionProps,
Modal,
ModalProps,
};

120 changes: 120 additions & 0 deletions src/framework/ui/topNavigationBar/topNavigationBar.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import {
View,
StyleSheet,
ViewProps,
Text,
TextProps,
} from 'react-native';
import {
StyledComponentProps,
StyleType,
} from '@kitten/theme';
import { Props as ActionProps } from './topNavigationBarAction.component';

interface TopNavigationBarProps {
title?: string;
subtitle?: string;
leftControl?: React.ReactElement<ActionProps>;
rightControls?: React.ReactElement<ActionProps>[];
}

export type Props = TopNavigationBarProps & StyledComponentProps & ViewProps;

export class TopNavigationBar extends React.Component<Props> {

private getComponentStyle = (style: StyleType): StyleType => ({
container: {
backgroundColor: style.backgroundColor,
height: style.height,
paddingTop: style.paddingTop,
paddingBottom: style.paddingBottom,
paddingHorizontal: style.paddingHorizontal,
},
titleContainer: style['title.centered'] ? {
flex: 3,
alignItems: 'center',
} : {
flex: 1,
paddingHorizontal: style.paddingHorizontal,
},
leftControlContainer: style['title.centered'] ? {
flex: 1,
} : null,
rightControlsContainer: style['title.centered'] ? {
flex: 1,
} : null,
title: {
color: style['title.color'],
fontSize: style['title.fontSize'],
fontWeight: style['title.fontWeight'],
},
subtitle: {
color: style['subtitle.color'],
fontSize: style['subtitle.fontSize'],
fontWeight: style['subtitle.fontWeight'],
},
});

private hasText(text: string): boolean {
return text && text.length !== 0;
}

private renderText(text: string, style: StyleType): React.ReactElement<TextProps> | null {
return this.hasText(text) ? <Text style={style}>{text}</Text> : null;
}

private renderRightControl(item: React.ReactElement<ActionProps>,
index: number): React.ReactElement<ActionProps> | null {

return item ? React.cloneElement(item, {
key: index,
isLastItem: React.Children.count(this.props.rightControls) - 1 === index,
}) : null;
}

private renderRightControls(): React.ReactElement<ActionProps>[] | null {
return React.Children
.map(this.props.rightControls, (item: React.ReactElement<ActionProps>, i: number) =>
this.renderRightControl(item, i));
}

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

return (
<View
{...this.props}
style={[styles.container, componentStyles.container, this.props.style]}
>
<View style={[componentStyles.leftControlContainer, styles.leftControlContainer]}>
{this.props.leftControl}
</View>
<View style={componentStyles.titleContainer}>
{this.renderText(this.props.title, componentStyles.title)}
{this.renderText(this.props.subtitle, componentStyles.subtitle)}
</View>
<View style={[styles.rightControlsContainer, componentStyles.rightControlsContainer]}>
{this.renderRightControls()}
</View>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
leftControlContainer: {
flexDirection: 'row',
justifyContent: 'flex-start',
},
rightControlsContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
});
59 changes: 59 additions & 0 deletions src/framework/ui/topNavigationBar/topNavigationBar.spec.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ThemeMappingType } from 'eva/packages/common';
import { ThemeType } from '@kitten/theme';

export const mapping: ThemeMappingType = {
TopNavigationBar: {
meta: {
variants: {},
states: [],
},
appearance: {
default: {
mapping: {
height: 46,
paddingTop: 4,
paddingBottom: 12,
paddingHorizontal: 16,
backgroundColor: 'blue-primary',
'title.centered': false,
'title.color': 'white',
'title.fontSize': 16,
'title.fontWeight': '600',
'subtitle.color': 'white',
'subtitle.fontSize': 12,
'subtitle.fontWeight': '400',
},
},
'title-centered': {
mapping: {
'title.centered': true,
},
},
},
},
TopNavigationBarAction: {
meta: {
variants: {},
states: [],
},
appearance: {
default: {
mapping: {
width: 25,
height: 25,
marginRight: 8,
},
},
},
},
};

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',
};
121 changes: 121 additions & 0 deletions src/framework/ui/topNavigationBar/topNavigationBar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import {
render,
fireEvent,
shallow,
} from 'react-native-testing-library';
import {
styled,
StyleProvider,
StyleProviderProps,
} from '@kitten/theme';
import {
TopNavigationBar as TopNavigationBarComponent,
Props as TopNavigationBarProps,
} from './topNavigationBar.component';
import {
TopNavigationBarAction as TopNavigationBarActionComponent,
Props as TopNavigationBaActionProps,
} from './topNavigationBarAction.component';
import * as config from './topNavigationBar.spec.config';
import { TouchableWithoutFeedback } from 'react-native';

const TopNavigationBar = styled<TopNavigationBarComponent, TopNavigationBarProps>(TopNavigationBarComponent);
const TopNavigationBarAction =
styled<TopNavigationBarActionComponent, TopNavigationBaActionProps>(TopNavigationBarActionComponent);

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

const ActionMock = (props?: TopNavigationBaActionProps): React.ReactElement<StyleProviderProps> => (
<StyleProvider mapping={config.mapping} theme={config.theme} styles={{}}>
<TopNavigationBarAction {...props} />
</StyleProvider>
);

const iconSourceUri: string = 'https://pngimage.net/wp-content/uploads/2018/05/back-icon-png-6.png';
const testIdLeftAction: string = '@top-navbar-left';
const testIdRightAction1: string = '@top-navbar-right-1';
const testIdRightAction2: string = '@top-navbar-right-2';
const testIdRightAction3: string = '@top-navbar-right-3';

describe('@top-navigation-bar/action', () => {

it('* bar/title', () => {
const { output } = shallow(<Mock title='Test'/>);
expect(output).toMatchSnapshot();
});

it('* bar/subtitle', () => {
const { output } = shallow(<Mock title='Test' subtitle='Subtitle'/>);
expect(output).toMatchSnapshot();
});

it('* bar/appearance: title-centered', () => {
const { output } = shallow(<Mock appearance='title-centered' title='Test'/>);
expect(output).toMatchSnapshot();
});

it('* bar/with actions', () => {
const onLeftControl = jest.fn();
const onRightControl1 = jest.fn();
const onRightControl2 = jest.fn();
const onRightControl3 = jest.fn();

const component =
<Mock
title='Test'
subtitle='Subtitle'
leftControl={
<ActionMock testID={testIdLeftAction} iconSource={{ uri: iconSourceUri }} onPress={onLeftControl}/>
}
rightControls={[
<ActionMock testID={testIdRightAction1} iconSource={{ uri: iconSourceUri }} onPress={onRightControl1}/>,
<ActionMock testID={testIdRightAction2} iconSource={{ uri: iconSourceUri }} onPress={onRightControl2}/>,
<ActionMock testID={testIdRightAction3} iconSource={{ uri: iconSourceUri }} onPress={onRightControl3}/>,
]}
/>;
const renderedComponent = render(component);
const { output } = shallow(component);

expect(output).toMatchSnapshot();
fireEvent.press(renderedComponent.getByTestId(testIdLeftAction));
fireEvent.press(renderedComponent.getByTestId(testIdRightAction1));
fireEvent.press(renderedComponent.getByTestId(testIdRightAction2));
fireEvent.press(renderedComponent.getByTestId(testIdRightAction3));
expect(onLeftControl).toHaveBeenCalled();
expect(onRightControl1).toHaveBeenCalled();
expect(onRightControl2).toHaveBeenCalled();
expect(onRightControl3).toHaveBeenCalled();
});

it('* action/with icon uri', () => {
const { output } = shallow(<ActionMock iconSource={{uri: iconSourceUri}}/>);
expect(output).toMatchSnapshot();
});

it('* action/is last item check (true)', () => {
const { output } = shallow(<ActionMock iconSource={{uri: iconSourceUri}} isLastItem={true}/>);
expect(output).toMatchSnapshot();
});

it('* action/is last item check (false)', () => {
const { output } = shallow(<ActionMock iconSource={{uri: iconSourceUri}} isLastItem={false}/>);
expect(output).toMatchSnapshot();
});

it('* action/on press check', () => {
const onPress = jest.fn();
const component = <ActionMock iconSource={{uri: iconSourceUri}} onPress={onPress}/>;
const renderedComponent = render(component);
const { output } = shallow(component);

fireEvent.press(renderedComponent.getByType(TouchableWithoutFeedback));
expect(onPress).toHaveBeenCalled();
expect(output).toMatchSnapshot();
});

});
Loading

0 comments on commit 3421311

Please sign in to comment.