Support for multiple values in the Phone field (#6882)

### Description

- This is the first PR on Phones field;


- We are introducing new field type(Phones)


- We are Forbidding creation of Phone field


- We Added support for filtering and sorting on Phones field


- We are using the same display mode as used on the Links field type
(chips), check the Domain field of the Company object


- We are also using the same logic of the link when editing the field

**How to Test**

1. Checkout to TWNTY-6260 branch
2. Reset database using "npx nx database:reset twenty-server" command
3. Add custom field of type Phones in settings/data-model

**Loom Video:**\

<https://www.loom.com/share/3c981260be254dcf851256d020a20ab0?sid=58507361-3a3b-452c-9de8-b5b1abda70ac>

### Refs

#6260

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
gitstart-app[bot]
2024-09-11 11:15:04 +02:00
committed by GitHub
parent 91187dcf82
commit 846953b0f4
52 changed files with 793 additions and 64 deletions

View File

@ -24,7 +24,7 @@ export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
}
const URI = parsedPhoneNumber.getURI();
const formattedNational = parsedPhoneNumber?.formatNational();
const formatedPhoneNumber = parsedPhoneNumber.formatInternational();
return (
<ContactLink
@ -33,7 +33,7 @@ export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
event.stopPropagation();
}}
>
{formattedNational || value}
{formatedPhoneNumber || value}
</ContactLink>
);
};

View File

@ -0,0 +1,87 @@
import styled from '@emotion/styled';
import { useMemo } from 'react';
import { THEME_COMMON } from 'twenty-ui';
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
import { parsePhoneNumber } from 'libphonenumber-js';
import { isDefined } from '~/utils/isDefined';
type PhonesDisplayProps = {
value?: FieldPhonesValue;
isFocused?: boolean;
};
const themeSpacing = THEME_COMMON.spacingMultiplicator;
const StyledContainer = styled.div`
align-items: center;
display: flex;
gap: ${themeSpacing * 1}px;
justify-content: flex-start;
max-width: 100%;
overflow: hidden;
width: 100%;
`;
export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
const phones = useMemo(
() =>
[
value?.primaryPhoneNumber
? {
number: value.primaryPhoneNumber,
countryCode: value.primaryPhoneCountryCode,
}
: null,
...(value?.additionalPhones ?? []),
]
.filter(isDefined)
.map(({ number, countryCode }) => {
return {
number,
countryCode,
};
}),
[
value?.primaryPhoneNumber,
value?.primaryPhoneCountryCode,
value?.additionalPhones,
],
);
return isFocused ? (
<ExpandableList isChipCountDisplayed>
{phones.map(({ number, countryCode }, index) => {
const parsedPhone = parsePhoneNumber(countryCode + number);
const URI = parsedPhone.getURI();
return (
<RoundedLink
key={index}
href={URI}
label={parsedPhone.formatInternational()}
/>
);
})}
</ExpandableList>
) : (
<StyledContainer>
{phones.map(({ number, countryCode }, index) => {
const parsedPhone = parsePhoneNumber(countryCode + number);
const URI = parsedPhone.getURI();
return (
<RoundedLink
key={index}
href={URI}
label={parsedPhone.formatInternational()}
/>
);
})}
</StyledContainer>
);
};

View File

@ -1,6 +1,7 @@
import { forwardRef, InputHTMLAttributes, ReactNode, useRef } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { forwardRef, InputHTMLAttributes, ReactNode, useRef } from 'react';
import 'react-phone-number-input/style.css';
import { RGBA, TEXT_INPUT_STYLE } from 'twenty-ui';
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
@ -43,7 +44,9 @@ const StyledRightContainer = styled.div`
transform: translateY(-50%);
`;
type DropdownMenuInputProps = InputHTMLAttributes<HTMLInputElement> & {
type HTMLInputProps = InputHTMLAttributes<HTMLInputElement>;
export type DropdownMenuInputProps = HTMLInputProps & {
hotkeyScope?: string;
onClickOutside?: () => void;
onEnter?: () => void;
@ -51,6 +54,12 @@ type DropdownMenuInputProps = InputHTMLAttributes<HTMLInputElement> & {
onShiftTab?: () => void;
onTab?: () => void;
rightComponent?: ReactNode;
renderInput?: (props: {
value: HTMLInputProps['value'];
onChange: HTMLInputProps['onChange'];
autoFocus: HTMLInputProps['autoFocus'];
placeholder: HTMLInputProps['placeholder'];
}) => React.ReactNode;
};
export const DropdownMenuInput = forwardRef<
@ -71,6 +80,7 @@ export const DropdownMenuInput = forwardRef<
onShiftTab,
onTab,
rightComponent,
renderInput,
},
ref,
) => {
@ -90,14 +100,23 @@ export const DropdownMenuInput = forwardRef<
return (
<StyledInputContainer className={className}>
<StyledInput
autoFocus={autoFocus}
value={value}
placeholder={placeholder}
onChange={onChange}
ref={combinedRef}
withRightComponent={!!rightComponent}
/>
{renderInput ? (
renderInput({
value,
onChange,
autoFocus,
placeholder,
})
) : (
<StyledInput
autoFocus={autoFocus}
value={value}
placeholder={placeholder}
onChange={onChange}
ref={combinedRef}
withRightComponent={!!rightComponent}
/>
)}
{!!rightComponent && (
<StyledRightContainer>{rightComponent}</StyledRightContainer>
)}