From 4f7e1fb60ed4d5e8fc498c1fdcef7de1a62dabe1 Mon Sep 17 00:00:00 2001 From: Mustajab Ikram Date: Fri, 25 Aug 2023 22:12:22 +0530 Subject: [PATCH] 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 --- front/package.json | 2 + .../PhoneEditableField.stories.tsx | 27 ++++ .../people/constants/peopleViewFields.tsx | 5 +- .../ui/editable-field/types/FieldMetadata.ts | 8 ++ .../ui/editable-field/types/ViewField.ts | 9 ++ .../types/guards/isViewFieldEmail.ts | 11 ++ .../types/guards/isViewFieldEmailValue.ts | 11 ++ .../email/components/EmailInputDisplay.tsx | 27 ++++ .../__stories__/EmailInputDisplay.stories.tsx | 37 +++++ .../phone/components/PhoneInputDisplay.tsx | 19 +-- .../__stories__/PhoneInputDisplay.stories.tsx | 38 +++++ .../ui/link/components/ContactLink.tsx | 38 +++++ .../modules/ui/table/constants/countries.json | 52 +++++++ .../components/GenericEditableCell.tsx | 6 +- .../GenericEditableDoubleTextCellEditMode.tsx | 2 +- .../components/GenericEditableEmailCell.tsx | 41 ++++++ .../GenericEditableEmailCellEditMode.tsx | 48 +++++++ .../components/GenericEditablePhoneCell.tsx | 1 + .../GenericEditablePhoneCellEditMode.tsx | 4 +- .../type/components/PhoneCellEdit.tsx | 130 ++++++++++++++++++ front/yarn.lock | 29 ++++ 21 files changed, 524 insertions(+), 21 deletions(-) create mode 100644 front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx create mode 100644 front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts create mode 100644 front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts create mode 100644 front/src/modules/ui/input/email/components/EmailInputDisplay.tsx create mode 100644 front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx create mode 100644 front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx create mode 100644 front/src/modules/ui/link/components/ContactLink.tsx create mode 100644 front/src/modules/ui/table/constants/countries.json create mode 100644 front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx create mode 100644 front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx create mode 100644 front/src/modules/ui/table/editable-cell/type/components/PhoneCellEdit.tsx diff --git a/front/package.json b/front/package.json index fbbbb3fd12..e14fc9c532 100644 --- a/front/package.json +++ b/front/package.json @@ -30,6 +30,7 @@ "graphql": "^16.6.0", "hex-rgb": "^5.0.0", "immer": "^10.0.2", + "intl-tel-input": "^18.2.1", "js-cookie": "^3.0.5", "js-levenshtein": "^1.1.6", "jwt-decode": "^3.1.2", @@ -133,6 +134,7 @@ "@testing-library/user-event": "^13.5.0", "@types/apollo-upload-client": "^17.0.2", "@types/deep-equal": "^1.0.1", + "@types/intl-tel-input": "^18.1.1", "@types/jest": "^27.5.2", "@types/js-cookie": "^3.0.3", "@types/lodash.debounce": "^4.0.7", diff --git a/front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx b/front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx new file mode 100644 index 0000000000..c6e66ba1d4 --- /dev/null +++ b/front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx @@ -0,0 +1,27 @@ +import { BrowserRouter } from 'react-router-dom'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { PhoneCellEdit } from '@/ui/table/editable-cell/type/components/PhoneCellEdit'; +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +const meta: Meta = { + title: 'Modules/People/EditableFields/PhoneEditableField', + component: PhoneCellEdit, + decorators: [ + (Story) => ( + + + + ), + ComponentDecorator, + ], + args: { + value: '+33714446494', + autoFocus: true, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/front/src/modules/people/constants/peopleViewFields.tsx b/front/src/modules/people/constants/peopleViewFields.tsx index fa539e845e..2194e3b1e8 100644 --- a/front/src/modules/people/constants/peopleViewFields.tsx +++ b/front/src/modules/people/constants/peopleViewFields.tsx @@ -2,6 +2,7 @@ import { ViewFieldDateMetadata, ViewFieldDefinition, ViewFieldDoubleTextChipMetadata, + ViewFieldEmailMetadata, ViewFieldMetadata, ViewFieldPhoneMetadata, ViewFieldRelationMetadata, @@ -45,11 +46,11 @@ export const peopleViewFields: ViewFieldDefinition[] = [ columnSize: 150, columnOrder: 2, metadata: { - type: 'text', + type: 'email', fieldName: 'email', placeHolder: 'Ema​il', // Hack: Fake character to prevent password-manager from filling the field }, - } satisfies ViewFieldDefinition, + } satisfies ViewFieldDefinition, { id: 'company', columnLabel: 'Company', diff --git a/front/src/modules/ui/editable-field/types/FieldMetadata.ts b/front/src/modules/ui/editable-field/types/FieldMetadata.ts index dc2f4fa66a..a5f8312227 100644 --- a/front/src/modules/ui/editable-field/types/FieldMetadata.ts +++ b/front/src/modules/ui/editable-field/types/FieldMetadata.ts @@ -9,6 +9,7 @@ export type FieldType = | 'double-text-chip' | 'double-text' | 'number' + | 'email' | 'boolean' | 'date' | 'phone' @@ -40,6 +41,11 @@ export type FieldNumberMetadata = { placeHolder: string; }; +export type FieldEmailMetadata = { + fieldName: string; + placeHolder: string; +}; + export type FieldRelationMetadata = { relationType: Entity; fieldName: string; @@ -82,6 +88,7 @@ export type FieldMetadata = | FieldPhoneMetadata | FieldURLMetadata | FieldNumberMetadata + | FieldEmailMetadata | FieldDateMetadata | FieldProbabilityMetadata; @@ -92,6 +99,7 @@ export type FieldDateValue = string; export type FieldPhoneValue = string; export type FieldURLValue = string; export type FieldNumberValue = number | null; +export type FieldEmailValue = string; export type FieldProbabilityValue = number; export type FieldDoubleTextValue = { diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts index db2fea2f07..e450dfc6e3 100644 --- a/front/src/modules/ui/editable-field/types/ViewField.ts +++ b/front/src/modules/ui/editable-field/types/ViewField.ts @@ -10,6 +10,7 @@ export type ViewFieldType = | 'number' | 'date' | 'phone' + | 'email' | 'url' | 'probability' | 'boolean' @@ -27,6 +28,12 @@ export type ViewFieldPhoneMetadata = { fieldName: string; }; +export type ViewFieldEmailMetadata = { + type: 'email'; + placeHolder: string; + fieldName: string; +}; + export type ViewFieldURLMetadata = { type: 'url'; placeHolder: string; @@ -99,6 +106,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & ( | ViewFieldDoubleTextChipMetadata | ViewFieldDoubleTextMetadata | ViewFieldPhoneMetadata + | ViewFieldEmailMetadata | ViewFieldURLMetadata | ViewFieldNumberMetadata | ViewFieldBooleanMetadata @@ -123,6 +131,7 @@ export type ViewFieldTextValue = string; export type ViewFieldChipValue = string; export type ViewFieldDateValue = string; export type ViewFieldPhoneValue = string; +export type ViewFieldEmailValue = string; export type ViewFieldBooleanValue = boolean; export type ViewFieldMoneyValue = number; export type ViewFieldURLValue = string; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts new file mode 100644 index 0000000000..10d77c3fdd --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmail.ts @@ -0,0 +1,11 @@ +import { + ViewFieldDefinition, + ViewFieldEmailMetadata, + ViewFieldMetadata, +} from '../ViewField'; + +export function isViewFieldEmail( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.metadata.type === 'email'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts new file mode 100644 index 0000000000..f7c23ea448 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldEmailValue.ts @@ -0,0 +1,11 @@ +import { ViewFieldEmailValue } from '../ViewField'; + +export function isViewFieldEmailValue( + fieldValue: unknown, +): fieldValue is ViewFieldEmailValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/input/email/components/EmailInputDisplay.tsx b/front/src/modules/ui/input/email/components/EmailInputDisplay.tsx new file mode 100644 index 0000000000..be508ca1ab --- /dev/null +++ b/front/src/modules/ui/input/email/components/EmailInputDisplay.tsx @@ -0,0 +1,27 @@ +import { MouseEvent } from 'react'; + +import { ContactLink } from '@/ui/link/components/ContactLink'; + +function validateEmail(email: string) { + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailPattern.test(email.trim()); +} + +type OwnProps = { + value: string | null; +}; + +export function EmailInputDisplay({ value }: OwnProps) { + return value && validateEmail(value) ? ( + ) => { + event.stopPropagation(); + }} + > + {value} + + ) : ( + {value} + ); +} diff --git a/front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx b/front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx new file mode 100644 index 0000000000..af845aa56d --- /dev/null +++ b/front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { Meta, StoryObj } from '@storybook/react'; + +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +import { EmailInputDisplay } from '../EmailInputDisplay'; + +const meta: Meta = { + title: 'Modules/People/EmailInputDisplay', + component: EmailInputDisplay, + decorators: [ + (Story) => ( + + + + + + ), + ComponentDecorator, + ], + args: { + value: 'mustajab.ikram@google.com', + }, +}; + +export default meta; + +type Story = StoryObj; + +const StyledTestEmailContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.primary}; + display: flex; +`; +export const Default: Story = {}; diff --git a/front/src/modules/ui/input/phone/components/PhoneInputDisplay.tsx b/front/src/modules/ui/input/phone/components/PhoneInputDisplay.tsx index 8e2cd8e827..5261313232 100644 --- a/front/src/modules/ui/input/phone/components/PhoneInputDisplay.tsx +++ b/front/src/modules/ui/input/phone/components/PhoneInputDisplay.tsx @@ -1,18 +1,7 @@ import { MouseEvent } from 'react'; -import styled from '@emotion/styled'; import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js'; -import { RawLink } from '@/ui/link/components/RawLink'; - -const StyledRawLink = styled(RawLink)` - overflow: hidden; - - a { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -`; +import { ContactLink } from '@/ui/link/components/ContactLink'; type OwnProps = { value: string | null; @@ -20,15 +9,15 @@ type OwnProps = { export function PhoneInputDisplay({ value }: OwnProps) { return value && isValidPhoneNumber(value) ? ( - ) => { event.stopPropagation(); }} > {parsePhoneNumber(value, 'FR')?.formatInternational() || value} - + ) : ( - {value} + {value} ); } diff --git a/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx b/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx new file mode 100644 index 0000000000..9caa497370 --- /dev/null +++ b/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { Meta, StoryObj } from '@storybook/react'; + +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +import { PhoneInputDisplay } from '../PhoneInputDisplay'; // Adjust the import path as needed + +const meta: Meta = { + title: 'Modules/People/PhoneInputDisplay', + component: PhoneInputDisplay, + decorators: [ + (Story) => ( + + + + + + ), + ComponentDecorator, + ], + args: { + value: '+33788901234', + }, +}; + +export default meta; + +type Story = StoryObj; + +const StyledTestPhoneContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.primary}; + display: flex; +`; + +export const Default: Story = {}; diff --git a/front/src/modules/ui/link/components/ContactLink.tsx b/front/src/modules/ui/link/components/ContactLink.tsx new file mode 100644 index 0000000000..39786536c4 --- /dev/null +++ b/front/src/modules/ui/link/components/ContactLink.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { Link as ReactLink } from 'react-router-dom'; +import styled from '@emotion/styled'; + +type OwnProps = { + className?: string; + href: string; + children?: React.ReactNode; + onClick?: (event: React.MouseEvent) => void; +}; + +const StyledClickable = styled.div` + display: flex; + overflow: hidden; + white-space: nowrap; + + a { + color: inherit; + text-decoration: underline; + text-decoration-color: ${({ theme }) => theme.border.color.strong}; + + &:hover { + text-decoration-color: ${({ theme }) => theme.font.color.primary}; + } + } +`; + +export function ContactLink({ className, href, children, onClick }: OwnProps) { + return ( +
+ + + {children} + + +
+ ); +} diff --git a/front/src/modules/ui/table/constants/countries.json b/front/src/modules/ui/table/constants/countries.json new file mode 100644 index 0000000000..6fc987f3f6 --- /dev/null +++ b/front/src/modules/ui/table/constants/countries.json @@ -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" +} diff --git a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx index 941e964291..a36988ae6d 100644 --- a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx +++ b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx @@ -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 ; + } else if (isViewFieldText(fieldDefinition)) { return ; } else if (isViewFieldRelation(fieldDefinition)) { return ; diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx index 86e218e459..318a109345 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDoubleTextCellEditMode.tsx @@ -28,7 +28,7 @@ export function GenericEditableDoubleTextCellEditMode({ viewField }: OwnProps) { const [secondValue, setSecondValue] = useRecoilState( tableEntityFieldFamilySelector({ entityId: currentRowEntityId ?? '', - fieldName: viewField.metadata.firstValueFieldName, + fieldName: viewField.metadata.secondValueFieldName, }), ); diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx new file mode 100644 index 0000000000..f8ccdfc300 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx @@ -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; + editModeHorizontalAlign?: 'left' | 'right'; +}; + +export function GenericEditableEmailCell({ + viewField, + editModeHorizontalAlign, +}: OwnProps) { + const currentRowEntityId = useCurrentRowEntityId(); + + const fieldValue = useRecoilValue( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + return ( + + } + nonEditModeContent={} + > + ); +} diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx new file mode 100644 index 0000000000..e88587a44a --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCellEditMode.tsx @@ -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; +}; + +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( + 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 ( + + ); +} diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx index f44501f031..dd06da91b6 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCell.tsx @@ -31,6 +31,7 @@ export function GenericEditablePhoneCell({ return ( diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx index 64b13a75b9..9dc555b777 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditablePhoneCellEditMode.tsx @@ -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; @@ -38,7 +38,7 @@ export function GenericEditablePhoneCellEditMode({ viewField }: OwnProps) { } return ( - 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(null); + const wrapperRef = useRef(null); + + function handleSubmit() { + onSubmit(internalText); + } + + function handleCancel() { + setInternalText(value); + } + + function handleChange(event: ChangeEvent) { + 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 ( + + + + ); +} diff --git a/front/yarn.lock b/front/yarn.lock index dc08965c05..b701702508 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -5378,6 +5378,13 @@ dependencies: "@types/node" "*" +"@types/intl-tel-input@^18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@types/intl-tel-input/-/intl-tel-input-18.1.1.tgz#33a1f4a3a97b02f34d16c227d9083e3d69e6b322" + integrity sha512-lxxF5QhR57Q/KEaA+52x1mb8f4tVgFXpKWC6VCod1EpL9w61D6kzW5srLcMQrv0/fCZ1UsSJATqFowLeUhxQXQ== + dependencies: + "@types/jquery" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -5413,6 +5420,13 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/jquery@*": + version "3.5.16" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.16.tgz#632131baf30951915b0317d48c98e9890bdf051d" + integrity sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw== + dependencies: + "@types/sizzle" "*" + "@types/js-cookie@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e" @@ -5663,6 +5677,11 @@ dependencies: "@types/node" "*" +"@types/sizzle@*": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" + integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== + "@types/sockjs@^0.3.33": version "0.3.33" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" @@ -11453,6 +11472,16 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +intl-tel-input@^18.2.1: + version "18.2.1" + resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-18.2.1.tgz#17de678f5ccfd156e4d125750cab4da8bd57fcbd" + integrity sha512-wOm0/61kTtpYjOOW1bhHzC4G8Om+atTxHmg31FS0KD0LQ8k8BpgO925npyi4jlT/EK4+joABABZzz0/XeSgupQ== + invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"