-
Notifications
You must be signed in to change notification settings - Fork 959
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): radio group component (#237)
- Loading branch information
1 parent
4872a85
commit bfabf75
Showing
6 changed files
with
430 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
Oops, something went wrong.