Feat: Adjust the overlay style for changing the phone number's country (#1876)

* switched to dropdown menu component

* Use latest dropdown container

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Ayush Agrawal 2023-10-10 03:08:09 +05:30 committed by GitHub
parent c37a39febb
commit 18c8f26f38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 273 additions and 36 deletions

View File

@ -0,0 +1,3 @@
export enum CountryPickerHotkeyScope {
CountryPicker = 'country-picker',
}

View File

@ -0,0 +1,146 @@
import { useEffect, useMemo, useState } from 'react';
import { getCountries, getCountryCallingCode } from 'react-phone-number-input';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { hasFlag } from 'country-flag-icons';
import * as Flags from 'country-flag-icons/react/3x2';
import { CountryCallingCode } from 'libphonenumber-js';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
import { useDropdown } from '@/ui/dropdown/hooks/useDropdown';
import { IconChevronDown } from '@/ui/icon';
import { IconWorld } from '../constants/icons';
import { CountryPickerHotkeyScope } from '../Types/CountryPickerHotkeyScope';
import { CountryPickerDropdownSelect } from './CountryPickerDropdownSelect';
import 'react-phone-number-input/style.css';
type StyledDropdownButtonProps = {
isUnfolded: boolean;
};
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.xs};
color: ${({ color }) => color ?? 'none'};
cursor: pointer;
display: flex;
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
height: 32px;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
user-select: none;
&:hover {
filter: brightness(0.95);
}
`;
const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
svg {
align-items: center;
display: flex;
height: 16px;
justify-content: center;
}
`;
export type Country = {
countryCode: string;
countryName: string;
callingCode: CountryCallingCode;
Flag: Flags.FlagComponent;
};
export const CountryPickerDropdownButton = ({
value,
onChange,
}: {
value: string;
onChange: (countryCode: string) => void;
}) => {
const theme = useTheme();
const [selectedCountry, setSelectedCountry] = useState<Country>();
const { isDropdownOpen, closeDropdown } = useDropdown({
dropdownId: 'country-picker',
});
const handleChange = (countryCode: string) => {
onChange(countryCode);
closeDropdown();
};
const countries = useMemo<Country[]>(() => {
const regionNamesInEnglish = new Intl.DisplayNames(['en'], {
type: 'region',
});
const countryCodes = getCountries();
return countryCodes.reduce<Country[]>((result, countryCode) => {
const countryName = regionNamesInEnglish.of(countryCode);
if (!countryName) return result;
if (!hasFlag(countryCode)) return result;
const Flag = Flags[countryCode];
const callingCode = getCountryCallingCode(countryCode);
result.push({
countryCode,
countryName,
callingCode,
Flag,
});
return result;
}, []);
}, []);
useEffect(() => {
const country = countries.find(({ countryCode }) => countryCode === value);
if (country) {
setSelectedCountry(country);
}
}, [countries, value]);
return (
<DropdownMenu
dropdownId="country-picker"
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer>
{selectedCountry ? <selectedCountry.Flag /> : <IconWorld />}
<IconChevronDown size={theme.icon.size.sm} />
</StyledIconContainer>
</StyledDropdownButtonContainer>
}
dropdownComponents={
<CountryPickerDropdownSelect
countries={countries}
selectedCountry={selectedCountry}
onChange={handleChange}
/>
}
dropdownOffset={{ x: -60, y: -34 }}
/>
);
};

View File

@ -0,0 +1,108 @@
import { useMemo, useState } from 'react';
import styled from '@emotion/styled';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar';
import { Country } from './CountryPickerDropdownButton';
import 'react-phone-number-input/style.css';
const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
padding-right: ${({ theme }) => theme.spacing(1)};
svg {
align-items: center;
display: flex;
height: 16px;
justify-content: center;
}
`;
const StyledDropdownMenuContainer = styled.ul`
left: 0;
padding: 0;
position: absolute;
top: 24px;
`;
export const CountryPickerDropdownSelect = ({
countries,
selectedCountry,
onChange,
}: {
countries: Country[];
selectedCountry?: Country;
onChange: (countryCode: string) => void;
}) => {
const [searchFilter, setSearchFilter] = useState<string>('');
const filteredCountries = useMemo(
() =>
countries.filter(({ countryName }) =>
countryName
.toLocaleLowerCase()
.includes(searchFilter.toLocaleLowerCase()),
),
[countries, searchFilter],
);
return (
<>
<StyledDropdownMenuContainer data-select-disable>
<StyledDropdownMenu width={'240px'}>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) => setSearchFilter(event.currentTarget.value)}
autoFocus
/>
<StyledDropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredCountries?.length === 0 ? (
<MenuItem text="No result" />
) : (
<>
{selectedCountry && (
<MenuItemSelectAvatar
key={selectedCountry.countryCode}
selected={true}
onClick={() => onChange(selectedCountry.countryCode)}
text={`${selectedCountry.countryName} (+${selectedCountry.callingCode})`}
avatar={
<StyledIconContainer>
<selectedCountry.Flag />
</StyledIconContainer>
}
/>
)}
{filteredCountries.map(
({ countryCode, countryName, callingCode, Flag }) =>
selectedCountry?.countryCode === countryCode ? null : (
<MenuItemSelectAvatar
key={countryCode}
selected={selectedCountry?.countryCode === countryCode}
onClick={() => onChange(countryCode)}
text={`${countryName} (+${callingCode})`}
avatar={
<StyledIconContainer>
<Flag />
</StyledIconContainer>
}
/>
),
)}
</>
)}
</DropdownMenuItemsContainer>
</StyledDropdownMenu>
</StyledDropdownMenuContainer>
</>
);
};

View File

@ -2,8 +2,13 @@ import { useEffect, useRef, useState } from 'react';
import ReactPhoneNumberInput from 'react-phone-number-input';
import styled from '@emotion/styled';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents';
import { CountryPickerDropdownButton } from './CountryPickerDropdownButton';
import 'react-phone-number-input/style.css';
const StyledContainer = styled.div`
@ -17,39 +22,9 @@ const StyledContainer = styled.div`
`;
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
--PhoneInput-color--focus: transparent;
--PhoneInputCountryFlag-borderColor--focus: transparent;
--PhoneInputCountrySelect-marginRight: ${({ theme }) => theme.spacing(2)};
--PhoneInputCountrySelectArrow-color: ${({ theme }) =>
theme.font.color.tertiary};
--PhoneInputCountrySelectArrow-opacity: 1;
font-family: ${({ theme }) => theme.font.family};
height: 32px;
.PhoneInputCountry {
--PhoneInputCountryFlag-height: 12px;
--PhoneInputCountryFlag-width: 16px;
border-right: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
justify-content: center;
margin-left: ${({ theme }) => theme.spacing(2)};
}
.PhoneInputCountryIcon {
background: none;
border-radius: ${({ theme }) => theme.border.radius.xs};
box-shadow: none;
margin-right: 1px;
overflow: hidden;
&:focus {
box-shadow: none !important;
}
}
.PhoneInputCountrySelectArrow {
margin-right: ${({ theme }) => theme.spacing(2)};
}
.PhoneInputInput {
background: ${({ theme }) => theme.background.transparent.secondary};
border: none;
@ -111,12 +86,17 @@ export const PhoneInput = ({
return (
<StyledContainer ref={wrapperRef}>
<StyledCustomPhoneInput
autoFocus={autoFocus}
placeholder="Phone number"
value={value}
onChange={setInternalValue}
/>
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
<StyledCustomPhoneInput
autoFocus={autoFocus}
placeholder="Phone number"
value={value}
onChange={setInternalValue}
international={true}
withCountryCallingCode={true}
countrySelectComponent={CountryPickerDropdownButton}
/>
</RecoilScope>
</StyledContainer>
);
};