Feat/phone email link enhancements (#1172)
* feat: Add type guards for ViewField email values and definitions, update ViewFieldTypes & peopleViewFields * feat: use ContactLink for EditablePhoneCell & create EditableEmailCell & EmailInputDisplay comp * fix: set second value for field * enhance: add edit btn for phone cell * feat: install dependencies intl-tel-input * feat: add phone cell input & connect intl-tel-input * fix: resolve rebase errors * fix: remove placeholder * feat(storybook): create stories for EmailInputDisplay, PhoneInputDisplay, and PhoneEditableField components --------- Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
52
front/src/modules/ui/table/constants/countries.json
Normal file
52
front/src/modules/ui/table/constants/countries.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"ch": "Switzerland",
|
||||
"de": "Germany",
|
||||
"ca": "Canada",
|
||||
"us": "US",
|
||||
"se": "Sweden",
|
||||
"jp": "Japan",
|
||||
"au": "Australia",
|
||||
"gb": "UK",
|
||||
"fr": "France",
|
||||
"dk": "Denmark",
|
||||
"nz": "New Zealand",
|
||||
"nl": "Netherlands",
|
||||
"no": "Norway",
|
||||
"it": "Italy",
|
||||
"fi": "Finland",
|
||||
"es": "Spain",
|
||||
"cn": "China",
|
||||
"be": "Belgium",
|
||||
"sg": "Singapore",
|
||||
"kr": "South Korea",
|
||||
"ae": "UAE",
|
||||
"at": "Austria",
|
||||
"ie": "Ireland",
|
||||
"lu": "Luxembourg",
|
||||
"gr": "Greece",
|
||||
"pt": "Portugal",
|
||||
"br": "Brazil",
|
||||
"th": "Thailand",
|
||||
"qa": "Qatar",
|
||||
"tr": "Turkey",
|
||||
"in": "India",
|
||||
"pl": "Poland",
|
||||
"mx": "Mexico",
|
||||
"sa": "Saudi Arabia",
|
||||
"eg": "Egypt",
|
||||
"ru": "Russia",
|
||||
"il": "Israel",
|
||||
"ar": "Argentina",
|
||||
"my": "Malaysia",
|
||||
"cr": "Costa Rica",
|
||||
"id": "Indonesia",
|
||||
"za": "South Africa",
|
||||
"ma": "Morocco",
|
||||
"cz": "Czechia",
|
||||
"hr": "Croatia",
|
||||
"ph": "Philippines",
|
||||
"vn": "Vietnam",
|
||||
"hu": "Hungary",
|
||||
"cl": "Chile",
|
||||
"pe": "Peru"
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewField
|
||||
import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate';
|
||||
import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText';
|
||||
import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip';
|
||||
import { isViewFieldEmail } from '@/ui/editable-field/types/guards/isViewFieldEmail';
|
||||
import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney';
|
||||
import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber';
|
||||
import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone';
|
||||
@ -19,6 +20,7 @@ import { GenericEditableChipCell } from '../type/components/GenericEditableChipC
|
||||
import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell';
|
||||
import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell';
|
||||
import { GenericEditableDoubleTextChipCell } from '../type/components/GenericEditableDoubleTextChipCell';
|
||||
import { GenericEditableEmailCell } from '../type/components/GenericEditableEmailCell';
|
||||
import { GenericEditableMoneyCell } from '../type/components/GenericEditableMoneyCell';
|
||||
import { GenericEditableNumberCell } from '../type/components/GenericEditableNumberCell';
|
||||
import { GenericEditablePhoneCell } from '../type/components/GenericEditablePhoneCell';
|
||||
@ -31,7 +33,9 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
export function GenericEditableCell({ viewField: fieldDefinition }: OwnProps) {
|
||||
if (isViewFieldText(fieldDefinition)) {
|
||||
if (isViewFieldEmail(fieldDefinition)) {
|
||||
return <GenericEditableEmailCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldText(fieldDefinition)) {
|
||||
return <GenericEditableTextCell viewField={fieldDefinition} />;
|
||||
} else if (isViewFieldRelation(fieldDefinition)) {
|
||||
return <GenericEditableRelationCell fieldDefinition={fieldDefinition} />;
|
||||
|
||||
@ -28,7 +28,7 @@ export function GenericEditableDoubleTextCellEditMode({ viewField }: OwnProps) {
|
||||
const [secondValue, setSecondValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.firstValueFieldName,
|
||||
fieldName: viewField.metadata.secondValueFieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldEmailMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { EmailInputDisplay } from '@/ui/input/email/components/EmailInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
|
||||
|
||||
import { GenericEditableEmailCellEditMode } from './GenericEditableEmailCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldEmailMetadata>;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function GenericEditableEmailCell({
|
||||
viewField,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableEmailCellEditMode viewField={viewField} />
|
||||
}
|
||||
nonEditModeContent={<EmailInputDisplay value={fieldValue} />}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldEmailMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldEmailMetadata>;
|
||||
};
|
||||
|
||||
export function GenericEditableEmailCellEditMode({ viewField }: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
// TODO: we could use a hook that would return the field value with the right type
|
||||
const [fieldValue, setFieldValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName: viewField.metadata.fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const updateField = useUpdateEntityField();
|
||||
|
||||
function handleSubmit(newEmail: string) {
|
||||
if (newEmail === fieldValue) return;
|
||||
|
||||
setFieldValue(newEmail);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, viewField, newEmail);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -31,6 +31,7 @@ export function GenericEditablePhoneCell({
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
useEditButton
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditablePhoneCellEditMode viewField={viewField} />
|
||||
|
||||
@ -8,7 +8,7 @@ import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
|
||||
|
||||
import { TextCellEdit } from './TextCellEdit';
|
||||
import { PhoneCellEdit } from './PhoneCellEdit';
|
||||
|
||||
type OwnProps = {
|
||||
viewField: ViewFieldDefinition<ViewFieldPhoneMetadata>;
|
||||
@ -38,7 +38,7 @@ export function GenericEditablePhoneCellEditMode({ viewField }: OwnProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCellEdit
|
||||
<PhoneCellEdit
|
||||
placeholder={viewField.metadata.placeHolder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import intlTelInput from 'intl-tel-input';
|
||||
|
||||
import { hoverBackground } from '@/ui/theme/constants/effects';
|
||||
|
||||
import countries from '../../../constants/countries.json';
|
||||
import { useRegisterCloseCellHandlers } from '../../hooks/useRegisterCloseCellHandlers';
|
||||
|
||||
import 'intl-tel-input/build/css/intlTelInput.css';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.iti__country-list {
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
|
||||
.iti__country {
|
||||
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
|
||||
--vertical-padding: ${({ theme }) => theme.spacing(3)};
|
||||
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
height: calc(32px - 2 * var(--vertical-padding));
|
||||
|
||||
padding: var(--vertical-padding) var(--horizontal-padding);
|
||||
|
||||
${hoverBackground};
|
||||
|
||||
width: calc(100% - 2 * var(--horizontal-padding));
|
||||
}
|
||||
}
|
||||
|
||||
.iti__flag {
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
}
|
||||
|
||||
.iti__arrow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
|
||||
margin: 0;
|
||||
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
width: ${({ theme }) => theme.spacing(48)};
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
autoFocus?: boolean;
|
||||
value: string;
|
||||
onSubmit: (newText: string) => void;
|
||||
};
|
||||
|
||||
export function PhoneCellEdit({ autoFocus, value, onSubmit }: OwnProps) {
|
||||
const [internalText, setInternalText] = useState(value);
|
||||
const phoneInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(internalText);
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setInternalText(value);
|
||||
}
|
||||
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setInternalText(event.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInternalText(value);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (phoneInputRef.current) {
|
||||
intlTelInput(phoneInputRef.current, {
|
||||
utilsScript:
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/8.4.6/js/utils.js',
|
||||
initialCountry: 'auto',
|
||||
formatOnDisplay: true,
|
||||
localizedCountries: countries,
|
||||
onlyCountries: Object.keys(countries),
|
||||
preferredCountries: [],
|
||||
});
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
|
||||
|
||||
return (
|
||||
<StyledContainer ref={wrapperRef}>
|
||||
<StyledInput
|
||||
type="tel"
|
||||
autoFocus={autoFocus}
|
||||
ref={phoneInputRef}
|
||||
onChange={handleChange}
|
||||
value={internalText}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user