diff --git a/src/components/TextEditor/components/EditorRadio.js b/src/components/TextEditor/components/EditorRadio.js index e9363cf1..204414c4 100644 --- a/src/components/TextEditor/components/EditorRadio.js +++ b/src/components/TextEditor/components/EditorRadio.js @@ -1,6 +1,6 @@ import React from 'react'; import { CODE_AND_OUTPUT, CODE_ONLY, OUTPUT_ONLY } from '../../../constants'; -import Radio from '../../common/Radio.js'; +import Radio from '../../common/Radio'; const EditorRadio = function (props) { let options = []; diff --git a/src/components/common/Radio.js b/src/components/common/Radio.js deleted file mode 100644 index 25c4735d..00000000 --- a/src/components/common/Radio.js +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import '../../styles/Radio.scss'; -/** - * Props - * - * options: (array of jsons) content for each box - * each json should look like { - * display: "Text to be displayed", - * (optional) value: the hidden value correlating to the display value (defaults to the index) - * } - * defaultSelected: (same type as options' value key) the option that will be default selected - * CAREFUL: if handleClick causes Radio to be re-rendered, make sure you're fine - * with this variable always being the defaultSelected - * containerStyle: (json) overrides the style on the outer container - * optionStyle: (json) overrides the style on each individual option - * selectedOptionStyle: (json) overrides the style on the selected option - * bgColor: (string) color of the non selected options - * color: (string) color of the non selected options text - * selectedBgColor: (string) color of the selected option - * selectedColor: (string) color of the selected options text - * handleClick: (func) function to be called when an option is clicked - * should have 1 parameter (value) which is the value - * (from the options prop) that is selected (defaults to index) - * allowMultipleSelected: (bool) if true, allows multiple options to be selected - * changes handle click to be called with all selected values - */ - -export default class Radio extends React.Component { - constructor(props) { - super(props); - - let selected = this.props.defaultSelected; - if (this.props.allowMultipleSelected) { - selected = selected || []; - } - - this.state = { - selected, - }; - } - - updateSelectedState = (selected, alreadySelected) => { - if (this.props.allowMultipleSelected) { - let newState = this.state.selected; - if (alreadySelected) { - const i = this.state.selected.indexOf(selected); - if (i >= 0) newState.splice(i, 1); - } else { - newState = this.state.selected.concat([selected]); - } - if (this.props.handleClick) { - this.props.handleClick(newState); - } - this.setState({ selected: newState }); - } else { - if (this.props.handleClick) { - this.props.handleClick(selected); - } - this.setState({ selected }); - } - }; - - renderOption = ({ display, value }, index) => { - // if no value is provided, use the index - value = value || index; - - const isSelected = value === this.state.selected - || (this.props.allowMultipleSelected && this.state.selected.includes(value)); - // attach -selected if the value matches the selected state - const className = `radio-option${isSelected ? '-selected' : ''}`; - // add an id of radio-left if its the first option or radio-right if its the last option - const id = index === 0 ? 'radio-left' : index === this.props.options.length - 1 ? 'radio-right' : ''; - - let optionStyle; - if (isSelected) { - optionStyle = { - - ...this.props.optionStyle || {}, - ...this.props.selectedOptionStyle || {}, - ...(this.props.selectedBgColor ? { backgroundColor: this.props.selectedBgColor } : {}), - ...(this.props.selectedColor ? { color: this.props.selectedColor } : {}), - }; - } else { - optionStyle = { - - ...this.props.optionStyle || {}, - ...(this.props.bgColor ? { backgroundColor: this.props.bgColor } : {}), - ...(this.props.color ? { color: this.props.color } : {}), - }; - } - - return ( -
this.updateSelectedState(value, isSelected)} - style={optionStyle} - id={id} - key={index} - > - {display} -
- ); - }; - - render() { - const { containerStyle } = this.props; - - const options = this.props.options || []; - - return ( -
- {options.map(this.renderOption)} -
- ); - } -} diff --git a/src/components/common/Radio.test.js b/src/components/common/Radio.test.js index b5a5119e..d0db3711 100644 --- a/src/components/common/Radio.test.js +++ b/src/components/common/Radio.test.js @@ -1,6 +1,6 @@ import { shallow } from 'enzyme'; import React from 'react'; -import Radio from './Radio.js'; +import Radio from './Radio'; const validOptions = [ { @@ -43,10 +43,7 @@ describe('Radio', () => { // on click option, invalid handle click does not cause error const component = shallow(); expect(() => { - component - .find('.radio-option') - .at(0) - .simulate('click'); + component.find('.radio-option').at(0).simulate('click'); }).not.toThrow(); }); @@ -107,7 +104,10 @@ describe('Radio', () => { { expect(clickFn.mock.calls[0][0]).toBe('pear'); // clicking a non selected option causes the selected value to change - component - .find('.radio-option') - .at(0) - .simulate('click'); + component.find('.radio-option').at(0).simulate('click'); expect(clickFn.mock.calls[1][0]).toBe('banana'); expect(component.state().selected).toBe('banana'); expect(component.find('.radio-option-selected').text()).toBe('Nice'); @@ -163,10 +160,7 @@ describe('Radio', () => { expect(component2.state().selected).toStrictEqual(['banana', 'apple', 'pear']); // click on already selected value, make sure it gets removed - component2 - .find('.radio-option-selected') - .at(0) - .simulate('click'); + component2.find('.radio-option-selected').at(0).simulate('click'); expect(clickFn.mock.calls[1][0]).toStrictEqual(['apple', 'pear']); expect(component2.state().selected).toStrictEqual(['apple', 'pear']); }); diff --git a/src/components/common/Radio.tsx b/src/components/common/Radio.tsx new file mode 100644 index 00000000..00381d33 --- /dev/null +++ b/src/components/common/Radio.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import '../../styles/Radio.scss'; +/** + * Props + * + * options: (array of jsons) content for each box + * each json should look like { + * display: "Text to be displayed", + * (optional) value: the hidden value correlating to the display value (defaults to the index) + * } + * defaultSelected: (same type as options' value key) the option that will be default selected + * CAREFUL: if handleClick causes Radio to be re-rendered, make sure you're fine + * with this variable always being the defaultSelected + * containerStyle: (json) overrides the style on the outer container + * optionStyle: (json) overrides the style on each individual option + * selectedOptionStyle: (json) overrides the style on the selected option + * bgColor: (string) color of the non selected options + * color: (string) color of the non selected options text + * selectedBgColor: (string) color of the selected option + * selectedColor: (string) color of the selected options text + * handleClick: (func) function to be called when an option is clicked + * should have 1 parameter (value) which is the value + * (from the options prop) that is selected (defaults to index) + * allowMultipleSelected: (bool) if true, allows multiple options to be selected + * changes handle click to be called with all selected values + */ + +interface OptionItem { + display: string; + value: string; +} + +interface RadioProps { + defaultSelected: T; + allowMultipleSelected: boolean; + options: OptionItem[]; + containerStyle: any; + optionStyle: any; + selectedOptionStyle: any; + selectedBgColor: any; + selectedColor: any; + bgColor: any; + color: any; + handleClick: (arg0: string) => string; +} + +interface RadioState { + selected?: any; +} + +class Radio extends React.Component , RadioState> { + // static defaultProps: Partial> = defaultProps; + constructor(props: RadioProps) { + super(props); + + const { allowMultipleSelected, defaultSelected } = this.props; + + let selected: T | T[] | undefined = defaultSelected; + if (allowMultipleSelected) { + selected = selected || []; + } + + this.state = { + selected, + }; + } + + updateSelectedState = (selected_value: string, alreadySelected: boolean): void => { + const { allowMultipleSelected, handleClick } = this.props; + + if (allowMultipleSelected) { + const { selected } = this.state; + let newState = selected; + if (alreadySelected) { + const i = selected.indexOf(selected_value); + if (i >= 0) newState.splice(i, 1); + } else { + newState = selected.concat([selected_value]); + } + if (handleClick) { + handleClick(newState); + } + this.setState({ selected: newState }); + } else { + if (handleClick) { + handleClick(selected_value); + } + this.setState({ selected: selected_value }); + } + }; + + renderOption = ({ display, value }: OptionItem, index: number): JSX.Element => { + // if no value is provided, use the index + // value = value || index; + const { selected } = this.state; + const { allowMultipleSelected, options } = this.props; + const isSelected = value === selected + || (allowMultipleSelected && selected.includes(value)); + // attach -selected if the value matches the selected state + const className = `radio-option${isSelected ? '-selected' : ''}`; + // add an id of radio-left if its the first option or radio-right if its the last option + let idValue; + if (index === 0) { + idValue = 'radio-left'; + } else if (index === options.length - 1) { + idValue = 'radio-right'; + } else { + idValue = ''; + } + const id = idValue; + + let newOptionStyle; + const { + optionStyle, selectedOptionStyle, selectedBgColor, selectedColor, bgColor, color, + } = this.props; + if (isSelected) { + newOptionStyle = { + + ...optionStyle || {}, + ...selectedOptionStyle || {}, + ...(selectedBgColor ? { backgroundColor: selectedBgColor } : {}), + ...(selectedColor ? { color: selectedColor } : {}), + }; + } else { + newOptionStyle = { + + ...optionStyle || {}, + ...(bgColor ? { backgroundColor: bgColor } : {}), + ...(color ? { color } : {}), + }; + } + + return ( +
this.updateSelectedState(value, isSelected)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + this.updateSelectedState(value, isSelected); + } + }} + style={newOptionStyle} + id={id} + key={index} + > + {display} +
+ ); + }; + + render() { + const { containerStyle, options } = this.props; + const optionsMap = options || []; + + return ( +
+ {optionsMap.map(this.renderOption)} +
+ ); + } +} + +export default Radio;