Fakultas Ilmu Komputer UI

Commit de52b3f1 authored by Muzaki Azami's avatar Muzaki Azami
Browse files

Merge branch 'PBI-1-form_components' into 'staging'

Implement reusable UI components

See merge request !3
parents b43ff2fc 6e347dee
Pipeline #64816 passed with stages
in 32 minutes and 58 seconds
......@@ -2,10 +2,17 @@ import 'react-native';
import React from 'react';
import { create } from 'react-test-renderer';
import { HeaderLeft } from './styles';
import App from '.';
jest.useFakeTimers();
describe('Application', () => {
it('renders correctly', () => {
create(<App />);
});
test('header left button renders correctly', () => {
create(<HeaderLeft />);
});
});
......@@ -9,6 +9,8 @@ import * as ROUTES from 'constants/routes';
import { navigation } from 'constants/navigation';
import { theme } from 'styles/theme';
import { screenOptions } from './styles';
const Stack = createStackNavigator();
const App: FC = () => {
......@@ -16,9 +18,19 @@ const App: FC = () => {
<SafeAreaProvider>
<ThemeProvider theme={theme}>
<NavigationContainer>
<Stack.Navigator initialRouteName={ROUTES.home}>
{navigation.map(({ name, component }, i) => (
<Stack.Screen name={name} component={component} key={i} />
<Stack.Navigator
initialRouteName={ROUTES.home}
screenOptions={screenOptions}>
{navigation.map((nav, i) => (
<Stack.Screen
key={`nav${i}`}
name={nav.name}
component={nav.component}
options={{
title: nav.header,
headerShown: Boolean(nav.header),
}}
/>
))}
</Stack.Navigator>
</NavigationContainer>
......
import React from 'react';
import {
HeaderBackButton,
StackHeaderLeftButtonProps,
StackNavigationOptions,
} from '@react-navigation/stack';
import { colors, typographyStyles } from 'styles';
export const HeaderLeft = (props: StackHeaderLeftButtonProps) => (
<HeaderBackButton {...props} />
);
export const screenOptions: StackNavigationOptions = {
cardStyle: {
backgroundColor: '#fff',
},
headerStyle: {
backgroundColor: 'white',
height: 62,
elevation: 0,
},
headerTintColor: colors.primary,
headerTitleStyle: typographyStyles.headingMedium,
headerTitleAlign: 'center',
headerLeft: HeaderLeft,
};
import React from 'react';
import { create, act } from 'react-test-renderer';
import { CheckBox } from 'react-native-elements';
import MultipleChoice from '.';
describe('MultipleChoice component', () => {
let value: number | null = null;
const props = {
questionNumber: 1,
questionLabel: 'Question',
totalQuestions: 2,
choices: [
{
label: 'Choice 1',
value: 1,
},
{
label: 'Choice 2',
value: 2,
},
],
value: value,
onChange: (v: number | null) => (value = v),
};
it('is controlled from external', () => {
const component = create(<MultipleChoice {...props} />);
const instance = component.root;
const radioButton = instance.findAllByType(CheckBox)[0];
act(() => radioButton.props.onPress());
expect(value).toBe(1);
});
it('executes onPress props if any radio button is pressed', () => {
let isPressed = false;
const component = create(
<MultipleChoice {...props} onPress={() => (isPressed = true)} />,
);
const instance = component.root;
const radioButton = instance.findAllByType(CheckBox)[0];
act(() => radioButton.props.onPress());
expect(isPressed).toBe(true);
});
});
import React, { FC } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-elements';
import { typographyStyles } from 'styles';
import RadioButton from '../RadioButton';
import { styles } from './styles';
import { Props } from './types';
const MultipleChoice: FC<Props> = ({
questionNumber,
totalQuestions,
questionLabel,
helperText,
choices,
value,
onChange,
onPress,
}) => (
<View>
<Text style={[typographyStyles.overlineBig, styles.spacing]}>
Pertanyaan {questionNumber} / {totalQuestions}
</Text>
<Text style={[typographyStyles.headingLarge, styles.spacing]}>
{questionLabel}
</Text>
<Text style={[typographyStyles.bodyMedium, styles.bigSpacing]}>
{helperText || 'Pilih satu yang paling cocok'}
</Text>
{choices.map((choice) => (
<RadioButton
key={choice.value}
title={choice.label}
checked={value === choice.value}
onPress={() => {
onChange(choice.value);
if (onPress) {
onPress();
}
}}
/>
))}
</View>
);
export default MultipleChoice;
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
spacing: {
marginBottom: 14,
},
bigSpacing: {
marginBottom: 24,
},
});
export interface Choice {
label: string;
value: number;
}
export interface MCQuestion {
questionLabel: string;
helperText?: string;
choiceList: string[];
}
export interface Props {
questionNumber: number;
totalQuestions: number;
questionLabel: string;
helperText?: string;
choices: Choice[];
value: number | null;
onChange: (_: number | null) => void;
onPress?: () => void;
}
import React from 'react';
import { create } from 'react-test-renderer';
import RadioButton from '.';
describe('RadioButton component', () => {
it('renders correctly', () => {
create(<RadioButton />);
});
});
import React, { FC } from 'react';
import { CheckBox, CheckBoxProps } from 'react-native-elements';
const RadioButton: FC<CheckBoxProps> = (props) => (
<CheckBox {...props} checkedIcon="dot-circle-o" uncheckedIcon="circle-o" />
);
export default RadioButton;
export { default as MultipleChoice } from './MultipleChoice';
export { default as RadioButton } from './RadioButton';
......@@ -5,4 +5,6 @@ export const colors = {
secondaryVariant: '#F2CD13',
neutral: '#C2CBE2',
neutralLight: '#F6F8FA',
formLabel: '#666666',
border: '#E0E0E0',
};
export { colors } from './colors';
export { typography } from './typography';
export { layoutStyles } from './layout';
export { theme } from './theme';
export { typography, typographyStyles } from './typography';
export const layoutStyles = {
paddingHorizontal: 20,
paddingVertical: 10,
};
import { Theme } from 'react-native-elements/dist/config/theme';
import { fontConfig } from './typography';
import { colors } from './colors';
import { fontConfig, typography } from './typography';
export const theme: Theme = {
Text: {
......@@ -8,4 +9,35 @@ export const theme: Theme = {
fontFamily: fontConfig.normal,
},
},
CheckBox: {
containerStyle: {
marginLeft: 0,
marginRight: 0,
borderColor: 'transparent',
backgroundColor: colors.neutralLight,
padding: 14,
},
textStyle: {
fontWeight: '500',
color: 'black',
...typography.bodyLarge,
},
size: 18,
checkedColor: colors.primary,
uncheckedColor: colors.neutral,
},
Input: {
labelStyle: {
fontWeight: '600',
color: colors.formLabel,
marginBottom: 4,
...typography.bodyMedium,
},
inputContainerStyle: {
borderColor: colors.border,
borderWidth: 1,
borderRadius: 4,
paddingHorizontal: 10,
},
},
};
......@@ -6,8 +6,55 @@ export const fontConfig = {
bold: 'Roboto-Medium',
};
export const typography: {
[_: string]: StyleProp<TextStyle>;
export const typography = {
displayLarge: {
fontFamily: fontConfig.bold,
fontSize: 40,
},
displayMedium: {
fontFamily: fontConfig.bold,
fontSize: 32,
},
headingLarge: {
fontFamily: fontConfig.bold,
fontSize: 24,
},
headingMedium: {
fontFamily: fontConfig.bold,
fontSize: 20,
},
bodyLarge: {
fontFamily: fontConfig.normal,
fontSize: 18,
},
bodyMedium: {
fontFamily: fontConfig.normal,
fontSize: 16,
},
bodySmall: {
fontFamily: fontConfig.normal,
fontSize: 14,
},
caption: {
fontFamily: fontConfig.normal,
fontSize: 12,
},
overlineBig: {
fontFamily: fontConfig.bold,
fontSize: 14,
textTransform: 'uppercase',
letterSpacing: 1.25,
},
overlineSmall: {
fontFamily: fontConfig.bold,
fontSize: 10,
textTransform: 'uppercase',
letterSpacing: 1.5,
},
};
export const typographyStyles: {
[_ in keyof typeof typography]: StyleProp<TextStyle>;
} = {
displayLarge: {
fontFamily: fontConfig.bold,
......@@ -26,15 +73,19 @@ export const typography: {
fontSize: 20,
},
bodyLarge: {
fontFamily: fontConfig.normal,
fontSize: 18,
},
bodyMedium: {
fontFamily: fontConfig.normal,
fontSize: 16,
},
bodySmall: {
fontFamily: fontConfig.normal,
fontSize: 14,
},
caption: {
fontFamily: fontConfig.normal,
fontSize: 12,
},
overlineBig: {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment