Skip to content

Commit

Permalink
feat(ui): radio group component (#237)
Browse files Browse the repository at this point in the history
  • Loading branch information
artyorsh authored and malashkevich committed Jan 18, 2019
1 parent 4872a85 commit bfabf75
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 5 deletions.
17 changes: 12 additions & 5 deletions src/framework/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { styled } from '@kitten/theme';
import {
Radio,
Props,
Radio as RadioComponent,
Props as RadioProps,
} from './radio/radio.component';
import {
RadioGroup as RadioGroupComponent,
Props as RadioGroupProps,
} from './radioGroup/radioGroup.component';

const StyledRadio = styled<Radio, Props>(Radio);
const Radio = styled<RadioComponent, RadioProps>(RadioComponent);
const RadioGroup = styled<RadioGroupComponent, RadioGroupProps>(RadioGroupComponent);

export {
StyledRadio as Radio,
Props as RadioProps,
Radio,
RadioGroup,
RadioProps,
RadioGroupProps,
};

81 changes: 81 additions & 0 deletions src/framework/ui/radioGroup/radioGroup.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import {
View,
ViewProps,
} from 'react-native';
import {
StyledComponentProps,
StyleType,
} from '@kitten/theme';
import { Props as ChildProps } from '../radio/radio.component';

type Child = React.ReactElement<ChildProps>;

interface RadioGroupProps {
selectedIndex?: number;
onChange?: (index: number) => void;
children: Child | Child[];
}

export type Props = RadioGroupProps & StyledComponentProps & ViewProps;

export class RadioGroup extends React.Component<Props> {

static defaultProps: Partial<Props> = {
selectedIndex: -1,
};

private onChildSelected = (index: number) => {
if (this.props.onChange) {
this.props.onChange(index);
}
};

private createChildrenArray = (source: Child | Child[]): Child[] => {
return Array.isArray(source) ? source : [source];
};

// We need to apply Attributes props
// because children provided by iterator should contain key prop

private createChildProps = (props: ChildProps, index: number): ChildProps & React.Attributes => {
return {
...props,
key: index,
checked: this.props.selectedIndex === index,
onChange: () => this.onChildSelected(index),
};
};

private renderChild = (element: Child, index: number): Child => {
const props: ChildProps & React.Attributes = this.createChildProps(element.props, index);

return React.cloneElement(element, props);
};

private renderChildren = (source: Child | Child[]): Child[] => {
const children: Child[] = this.createChildrenArray(source);

return children.map(this.renderChild);
};

private getComponentStyle = (style: StyleType): StyleType => {
return {
container: {
padding: style.padding,
},
};
};

render() {
const componentStyle: StyleType = this.getComponentStyle(this.props.themedStyle);

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

export const mapping: ThemeMappingType = {
RadioGroup: {
meta: {
variants: {},
states: [],
},
appearance: {
default: {
mapping: {
padding: 0,
},
},
},
},
Radio: {
meta: {
variants: {
status: [
'error',
],
size: [
'small',
'big',
],
},
states: [
'checked',
'disabled',
'active',
],
},
appearance: {
default: {
mapping: {
size: 36,
innerSize: 24,
highlightSize: 60,
borderWidth: 2,
borderColor: 'gray-primary',
selectColor: 'transparent',
highlightColor: 'transparent',
state: {
active: {
borderColor: 'gray-dark',
highlightColor: 'gray-light',
},
checked: {
borderColor: 'blue-primary',
selectColor: 'blue-primary',
},
disabled: {
borderColor: 'gray-light',
},
'active.checked': {
borderColor: 'blue-dark',
},
'checked.disabled': {
selectColor: 'gray-primary',
},
},
},
variant: {
status: {
error: {
mapping: {
borderColor: 'pink-primary',
state: {
checked: {
borderColor: 'pink-primary',
selectColor: 'pink-primary',
},
'active.checked': {
borderColor: 'pink-primary',
},
},
},
},
},
size: {
big: {
mapping: {
size: 42,
innerSize: 28,
highlightSize: 70,
},
},
small: {
mapping: {
size: 30,
innerSize: 20,
highlightSize: 50,
},
},
},
},
},
},
},
};

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',
};
95 changes: 95 additions & 0 deletions src/framework/ui/radioGroup/radioGroup.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { TouchableOpacity } from 'react-native';
import {
render,
fireEvent,
} from 'react-native-testing-library';
import {
styled,
StyleProvider,
StyleProviderProps,
} from '@kitten/theme';
import {
RadioGroup,
Props as RadioGroupProps,
} from './radioGroup.component';
import {
Radio as RadioComponent,
Props as RadioProps,
} from '../radio/radio.component';
import * as config from './radioGroup.spec.config';

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

const GroupMock = styled<RadioGroup, RadioGroupProps>(RadioGroup);

const ChildMock = styled<RadioComponent, RadioProps>(RadioComponent);

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

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

it('* ignores child `checked` prop', () => {
const component = render(
<Mock>
<ChildMock testID={childTestId0} checked={true}/>
</Mock>,
);

const child = component.getByTestId(childTestId0);

expect(child.props.checked).toEqual(false);
});

it('* ignores child `onChange` prop', () => {
const onChangeChild = jest.fn();

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

const childTouchable = component.getByTestId(childTestId0).findByType(TouchableOpacity);
fireEvent.press(childTouchable);

expect(onChangeChild).not.toBeCalled();
});

it('* initial selection performed properly', () => {
const component = render(
<Mock selectedIndex={0}>
<ChildMock testID={childTestId0} checked={false}/>
<ChildMock testID={childTestId1} checked={true}/>
</Mock>,
);

const child0 = component.getByTestId(childTestId0);
const child1 = component.getByTestId(childTestId1);

expect(child0.props.checked).toEqual(true);
expect(child1.props.checked).toEqual(false);
});

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

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

const childTouchable = component.getByTestId(childTestId1).findByType(TouchableOpacity);
fireEvent.press(childTouchable);

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

});
1 change: 1 addition & 0 deletions src/playground/src/ui/screen/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { HomeScreen } from './home.component';
export { RadioScreen } from './radio.component';
export { RadioGroupScreen } from './radioGroup.component';
Loading

0 comments on commit bfabf75

Please sign in to comment.