171 lines
		
	
	
	
		
			4.2 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			171 lines
		
	
	
	
		
			4.2 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2024 Signal Messenger, LLC
							 | 
						||
| 
								 | 
							
								// SPDX-License-Identifier: AGPL-3.0-only
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import React, { useState, useCallback, useMemo } from 'react';
							 | 
						||
| 
								 | 
							
								import Fuse from 'fuse.js';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import type { LocalizerType } from '../types/Util';
							 | 
						||
| 
								 | 
							
								import type { CountryDataType } from '../util/getCountryData';
							 | 
						||
| 
								 | 
							
								import { Modal } from './Modal';
							 | 
						||
| 
								 | 
							
								import { SearchInput } from './SearchInput';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type PropsType = Readonly<{
							 | 
						||
| 
								 | 
							
								  i18n: LocalizerType;
							 | 
						||
| 
								 | 
							
								  onChange: (region: string) => void;
							 | 
						||
| 
								 | 
							
								  value: string;
							 | 
						||
| 
								 | 
							
								  defaultRegion: string;
							 | 
						||
| 
								 | 
							
								  countries: ReadonlyArray<CountryDataType>;
							 | 
						||
| 
								 | 
							
								}>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function CountryCodeSelect({
							 | 
						||
| 
								 | 
							
								  i18n,
							 | 
						||
| 
								 | 
							
								  onChange,
							 | 
						||
| 
								 | 
							
								  value,
							 | 
						||
| 
								 | 
							
								  defaultRegion,
							 | 
						||
| 
								 | 
							
								  countries,
							 | 
						||
| 
								 | 
							
								}: PropsType): JSX.Element {
							 | 
						||
| 
								 | 
							
								  const index = useMemo(() => {
							 | 
						||
| 
								 | 
							
								    return new Fuse<CountryDataType>(countries, {
							 | 
						||
| 
								 | 
							
								      keys: [
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          name: 'displayName',
							 | 
						||
| 
								 | 
							
								          weight: 1,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          name: 'code',
							 | 
						||
| 
								 | 
							
								          weight: 0.5,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								      ],
							 | 
						||
| 
								 | 
							
								      threshold: 0.1,
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }, [countries]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const [isModalOpen, setIsModalOpen] = useState(false);
							 | 
						||
| 
								 | 
							
								  const [searchTerm, setSearchTerm] = useState('');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const selectedCountry = useMemo(() => {
							 | 
						||
| 
								 | 
							
								    return countries.find(({ region }) => region === value);
							 | 
						||
| 
								 | 
							
								  }, [countries, value]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const defaultCode = useMemo(() => {
							 | 
						||
| 
								 | 
							
								    return countries.find(({ region }) => region === defaultRegion)?.code ?? '';
							 | 
						||
| 
								 | 
							
								  }, [countries, defaultRegion]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const filteredCountries = useMemo(() => {
							 | 
						||
| 
								 | 
							
								    if (!searchTerm) {
							 | 
						||
| 
								 | 
							
								      return countries;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return index.search(searchTerm).map(({ item }) => item);
							 | 
						||
| 
								 | 
							
								  }, [countries, index, searchTerm]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const onShowModal = useCallback((ev: React.MouseEvent) => {
							 | 
						||
| 
								 | 
							
								    ev.preventDefault();
							 | 
						||
| 
								 | 
							
								    setIsModalOpen(true);
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const onCloseModal = useCallback(() => {
							 | 
						||
| 
								 | 
							
								    setIsModalOpen(false);
							 | 
						||
| 
								 | 
							
								    setSearchTerm('');
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const onSearchTermChange = useCallback(
							 | 
						||
| 
								 | 
							
								    (ev: React.ChangeEvent<HTMLInputElement>) => {
							 | 
						||
| 
								 | 
							
								      setSearchTerm(ev.target.value);
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    []
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const onCountryClick = useCallback(
							 | 
						||
| 
								 | 
							
								    (region: string) => {
							 | 
						||
| 
								 | 
							
								      onCloseModal();
							 | 
						||
| 
								 | 
							
								      onChange(region);
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    [onChange, onCloseModal]
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const modal = (
							 | 
						||
| 
								 | 
							
								    <Modal
							 | 
						||
| 
								 | 
							
								      i18n={i18n}
							 | 
						||
| 
								 | 
							
								      modalName="CountryCodeSelect__Modal"
							 | 
						||
| 
								 | 
							
								      moduleClassName="CountryCodeSelect__Modal"
							 | 
						||
| 
								 | 
							
								      hasXButton
							 | 
						||
| 
								 | 
							
								      padded={false}
							 | 
						||
| 
								 | 
							
								      title={i18n('icu:CountryCodeSelect__Modal__title')}
							 | 
						||
| 
								 | 
							
								      onClose={onCloseModal}
							 | 
						||
| 
								 | 
							
								    >
							 | 
						||
| 
								 | 
							
								      <SearchInput
							 | 
						||
| 
								 | 
							
								        i18n={i18n}
							 | 
						||
| 
								 | 
							
								        moduleClassName="CountryCodeSelect__Modal__Search"
							 | 
						||
| 
								 | 
							
								        onChange={onSearchTermChange}
							 | 
						||
| 
								 | 
							
								        placeholder={i18n('icu:search')}
							 | 
						||
| 
								 | 
							
								        value={searchTerm}
							 | 
						||
| 
								 | 
							
								      />
							 | 
						||
| 
								 | 
							
								      <div className="CountryCodeSelect__table">
							 | 
						||
| 
								 | 
							
								        {filteredCountries.map(({ displayName, region, code }) => {
							 | 
						||
| 
								 | 
							
								          return (
							 | 
						||
| 
								 | 
							
								            <CountryButton
							 | 
						||
| 
								 | 
							
								              key={region}
							 | 
						||
| 
								 | 
							
								              region={region}
							 | 
						||
| 
								 | 
							
								              displayName={displayName}
							 | 
						||
| 
								 | 
							
								              code={code}
							 | 
						||
| 
								 | 
							
								              onClick={onCountryClick}
							 | 
						||
| 
								 | 
							
								            />
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        })}
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								      <div className="CountryCodeSelect__grow" />
							 | 
						||
| 
								 | 
							
								    </Modal>
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    <>
							 | 
						||
| 
								 | 
							
								      <button type="button" className="CountryCodeSelect" onClick={onShowModal}>
							 | 
						||
| 
								 | 
							
								        <div className="CountryCodeSelect__text">
							 | 
						||
| 
								 | 
							
								          {selectedCountry?.displayName ??
							 | 
						||
| 
								 | 
							
								            i18n('icu:CountryCodeSelect__placeholder')}
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								        <div className="CountryCodeSelect__value">
							 | 
						||
| 
								 | 
							
								          {selectedCountry?.code ?? defaultCode}
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								        <div className="CountryCodeSelect__arrow" />
							 | 
						||
| 
								 | 
							
								      </button>
							 | 
						||
| 
								 | 
							
								      {isModalOpen ? modal : null}
							 | 
						||
| 
								 | 
							
								    </>
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type CountryButtonPropsType = Readonly<{
							 | 
						||
| 
								 | 
							
								  region: string;
							 | 
						||
| 
								 | 
							
								  displayName: string;
							 | 
						||
| 
								 | 
							
								  code: string;
							 | 
						||
| 
								 | 
							
								  onClick: (region: string) => void;
							 | 
						||
| 
								 | 
							
								}>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function CountryButton({
							 | 
						||
| 
								 | 
							
								  region,
							 | 
						||
| 
								 | 
							
								  displayName,
							 | 
						||
| 
								 | 
							
								  code,
							 | 
						||
| 
								 | 
							
								  onClick,
							 | 
						||
| 
								 | 
							
								}: CountryButtonPropsType): JSX.Element {
							 | 
						||
| 
								 | 
							
								  const onButtonClick = useCallback(
							 | 
						||
| 
								 | 
							
								    (ev: React.MouseEvent) => {
							 | 
						||
| 
								 | 
							
								      ev.preventDefault();
							 | 
						||
| 
								 | 
							
								      onClick(region);
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    [region, onClick]
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    <button
							 | 
						||
| 
								 | 
							
								      type="button"
							 | 
						||
| 
								 | 
							
								      className="CountryCodeSelect__CountryButton"
							 | 
						||
| 
								 | 
							
								      onClick={onButtonClick}
							 | 
						||
| 
								 | 
							
								    >
							 | 
						||
| 
								 | 
							
								      <div className="CountryCodeSelect__CountryButton__name">
							 | 
						||
| 
								 | 
							
								        {displayName}
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								      <div className="CountryCodeSelect__CountryButton__code">{code}</div>
							 | 
						||
| 
								 | 
							
								    </button>
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 |