Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/components/PhoneInput/PhoneInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,39 @@ describe('PhoneInput', () => {
expect(document.activeElement).toEqual(screen.getByLabelText('Phone Number'));
});

it('should only render countries from allowedCountries when provided', async () => {
render(<PhoneInput country={defaultCountry} allowedCountries={['DE', 'AD']} label="Phone Number" />);

fireEvent.keyDown(screen.getByText(defaultCountry.dialCode), { keyCode: 40 });

expect(await screen.findByText('Andorra +376')).toBeInTheDocument();
expect(screen.queryByText(/Afghanistan/)).not.toBeInTheDocument();
});

it('should not render the selected country value when it is not part of allowedCountries', () => {
render(<PhoneInput country={defaultCountry} allowedCountries={['AD']} label="Phone Number" />);

expect(screen.queryByText(defaultCountry.dialCode)).not.toBeInTheDocument();
});

it('should not render the selected country value when it is not a valid country', () => {
const invalidCountry = { value: 'POTATO', label: 'Potato', dialCode: '+0' };
render(<PhoneInput country={invalidCountry} label="Phone Number" />);

expect(screen.queryByText('Potato')).not.toBeInTheDocument();
expect(screen.queryByText('+0')).not.toBeInTheDocument();
});

it('should warn when the selected country is not among the available options', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

render(<PhoneInput country={defaultCountry} allowedCountries={['AD']} label="Phone Number" />);

expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('is not among the available options'));

consoleErrorSpy.mockRestore();
});

it('should call the change handler when typing in the national number input', () => {
const mockCountryChangeHandler = jest.fn();
const mockTextChangeHandler = jest.fn();
Expand Down
23 changes: 21 additions & 2 deletions src/components/PhoneInput/PhoneInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useRef } from 'react';
import styled from 'styled-components';
import warning from 'warning';
import {
compose,
layout,
Expand Down Expand Up @@ -64,6 +65,10 @@ interface PhoneInputProps
* Pass props directly to the internal SelectList component used to show prefixes. Any value from the `SelectList` component props are allowed, but props from the `PhoneInput` take precedence
*/
selectListProps?: SelectListProps;
/**
* Restricts the country list to only these ISO codes (e.g. ['DE', 'FR']). When omitted, all countries are shown.
*/
allowedCountries?: ReadonlyArray<string>;
}

const Box = styled.div<LayoutProps & WidthProps>`
Expand All @@ -83,6 +88,20 @@ const PhoneInput: React.FC<PhoneInputProps> = ({
const containerRef = useRef<HTMLDivElement>();
const spaceBetweenInputs = variant === 'boxed' ? '0.25rem' : '0.75rem';

const countries = props.allowedCountries
? COUNTRIES.filter(it => props.allowedCountries.includes(it.value))
: COUNTRIES;

// Avoid rendering a invalid option/country that isn't amongst allowedCountries
const selectedCountry = countries.find(it => it.value === props.country?.value) && props.country;

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
warning(
!props.country || Boolean(selectedCountry),
`[@freenow/wave] PhoneInput received a \`country\` ("${props.country?.value}") that is not among the ` +
'available options and will not be displayed. Ensure it is included in `allowedCountries`.'
);

const handleCountrySelection = value => {
if (props.onCountryChange) {
props.onCountryChange(value);
Expand All @@ -97,9 +116,9 @@ const PhoneInput: React.FC<PhoneInputProps> = ({
{...selectListProps}
id={`${props.id}-area-code`}
name={`${props.name}-area-code`}
value={props.country}
value={selectedCountry}
onChange={handleCountrySelection}
options={COUNTRIES.map(it => ({ ...it, label: `${it.label} ${it.dialCode}` }))}
options={countries.map(it => ({ ...it, label: `${it.label} ${it.dialCode}` }))}
placeholder=""
width="8rem"
components={{
Expand Down
7 changes: 7 additions & 0 deletions src/components/PhoneInput/docs/PhoneInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ export const Preselected: Story = {
}
};

export const AllowedCountries: Story = {
...Default,
args: {
allowedCountries: ['DE', 'ES', 'FR', 'GB', 'AD']
}
};

export const BottomLined: Story = {
...Default,
args: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/PhoneInput/docs/PhoneInput.storybook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as PhoneInputStories from './PhoneInput.stories';

The `PhoneInput` is a form component for inputting a phone number in international format with usage of country selector.

The component consists of two controls: a select to pick a prefix and an input to type in the phone number. The select shows all the country codes available world-wide.
The component consists of two controls: a select to pick a prefix and an input to type in the phone number. The select shows all the country codes available world-wide by default, or a restricted subset when the `allowedCountries` prop is provided.

<Primary />

Expand Down
Loading