This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { countries } from '../../../utils';
|
||||
|
||||
import CountryRow from './CountryRow';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Click handler for a single entry.
|
||||
*/
|
||||
onEntryClick: Function;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
height: '190px',
|
||||
width: '343px',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.palette.ui01
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This component displays the dropdown for the country picker.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function CountryDropdown({ onEntryClick }: IProps) {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<div className = { classes.container }>
|
||||
{countries.map(country => (
|
||||
<CountryRow
|
||||
country = { country }
|
||||
key = { `${country.code}` }
|
||||
onEntryClick = { onEntryClick } />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CountryDropdown;
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import Popover from '../../../../base/popover/components/Popover.web';
|
||||
import { setDialOutCountry, setDialOutNumber } from '../../../actions.web';
|
||||
import { getDialOutCountry, getDialOutNumber } from '../../../functions';
|
||||
import { getCountryFromDialCodeText } from '../../../utils';
|
||||
|
||||
import CountryDropDown from './CountryDropdown';
|
||||
import CountrySelector from './CountrySelector';
|
||||
|
||||
const PREFIX_REG = /^(00)|\+/;
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The country to dial out to.
|
||||
*/
|
||||
dialOutCountry: { code: string; dialCode: string; name: string; };
|
||||
|
||||
/**
|
||||
* The number to dial out to.
|
||||
*/
|
||||
dialOutNumber: string;
|
||||
|
||||
/**
|
||||
* Handler used when user presses 'Enter'.
|
||||
*/
|
||||
onSubmit: Function;
|
||||
|
||||
/**
|
||||
* Sets the dial out country.
|
||||
*/
|
||||
setDialOutCountry: Function;
|
||||
|
||||
/**
|
||||
* Sets the dial out number.
|
||||
*/
|
||||
setDialOutNumber: Function;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
border: 0,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
display: 'flex',
|
||||
backgroundColor: theme.palette.ui03
|
||||
},
|
||||
|
||||
input: {
|
||||
padding: '0 4px',
|
||||
margin: 0,
|
||||
border: 0,
|
||||
background: 'transparent',
|
||||
color: theme.palette.text01,
|
||||
flexGrow: 1,
|
||||
...theme.typography.bodyShortRegular
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const CountryPicker = (props: IProps) => {
|
||||
const [ isOpen, setIsOpen ] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { classes } = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const onChange = ({ target: { value: newValue } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (PREFIX_REG.test(newValue)) {
|
||||
const textWithDialCode = newValue.replace(PREFIX_REG, '');
|
||||
|
||||
if (textWithDialCode.length >= 4) {
|
||||
const country = getCountryFromDialCodeText(textWithDialCode);
|
||||
|
||||
if (country) {
|
||||
const rest = textWithDialCode.replace(country.dialCode, '');
|
||||
|
||||
props.setDialOutCountry(country);
|
||||
props.setDialOutNumber(rest);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
props.setDialOutNumber(newValue);
|
||||
};
|
||||
|
||||
const onCountrySelectorClick = () => {
|
||||
setIsOpen(open => !open);
|
||||
};
|
||||
|
||||
const onDropdownClose = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const onEntryClick = (country: { code: string; dialCode: string; name: string; }) => {
|
||||
props.setDialOutCountry(country);
|
||||
onDropdownClose();
|
||||
};
|
||||
|
||||
const onKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
props.onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
<Popover
|
||||
content = { <CountryDropDown onEntryClick = { onEntryClick } /> }
|
||||
onPopoverClose = { onDropdownClose }
|
||||
position = 'bottom'
|
||||
trigger = 'click'
|
||||
visible = { isOpen }>
|
||||
<div className = { classes.container }>
|
||||
<CountrySelector
|
||||
country = { props.dialOutCountry }
|
||||
onClick = { onCountrySelectorClick } />
|
||||
<input
|
||||
className = { classes.input }
|
||||
onChange = { onChange }
|
||||
onKeyPress = { onKeyPress }
|
||||
ref = { inputRef }
|
||||
value = { props.dialOutNumber } />
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
dialOutCountry: getDialOutCountry(state),
|
||||
dialOutNumber: getDialOutNumber(state)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux actions to the props of the component.
|
||||
*
|
||||
* @type {{
|
||||
* setDialOutCountry: Function,
|
||||
* setDialOutNumber: Function
|
||||
* }}
|
||||
*/
|
||||
const mapDispatchToProps = {
|
||||
setDialOutCountry,
|
||||
setDialOutNumber
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CountryPicker);
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Country of the entry.
|
||||
*/
|
||||
country: { code: string; dialCode: string; name: string; };
|
||||
|
||||
/**
|
||||
* Entry click handler.
|
||||
*/
|
||||
onEntryClick: Function;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
display: 'flex',
|
||||
padding: '10px',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.action03,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action03Hover
|
||||
}
|
||||
},
|
||||
|
||||
flag: {
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
|
||||
text: {
|
||||
color: theme.palette.text01,
|
||||
...theme.typography.bodyShortRegular,
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const CountryRow = ({ country, onEntryClick }: IProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
const _onClick = () => {
|
||||
onEntryClick(country);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { classes.container }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { _onClick }>
|
||||
<div className = { cx(classes.flag, 'iti-flag', country.code) } />
|
||||
<div className = { classes.text }>
|
||||
{`${country.name} (+${country.dialCode})`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CountryRow;
|
||||
@@ -0,0 +1,76 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconArrowDown } from '../../../../base/icons/svg';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Country object of the entry.
|
||||
*/
|
||||
country: { code: string; dialCode: string; name: string; };
|
||||
|
||||
/**
|
||||
* Click handler for the selector.
|
||||
*/
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
padding: '8px 10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: theme.palette.ui01,
|
||||
borderRight: `1px solid ${theme.palette.ui03}`,
|
||||
color: theme.palette.text01,
|
||||
...theme.typography.bodyShortRegular,
|
||||
position: 'relative',
|
||||
width: '88px',
|
||||
borderTopLeftRadius: theme.shape.borderRadius,
|
||||
borderBottomLeftRadius: theme.shape.borderRadius
|
||||
},
|
||||
|
||||
text: {
|
||||
flexGrow: 1
|
||||
},
|
||||
|
||||
flag: {
|
||||
marginRight: theme.spacing(2)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This component displays the country selector with the flag.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function CountrySelector({ country: { code, dialCode }, onClick }: IProps) {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}, [ onClick ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { classes.container }
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPressHandler }>
|
||||
<div className = { cx(classes.flag, 'iti-flag', code) } />
|
||||
<span className = { classes.text }>{`+${dialCode}`}</span>
|
||||
<Icon
|
||||
size = { 16 }
|
||||
src = { IconArrowDown } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CountrySelector;
|
||||
Reference in New Issue
Block a user