diff --git a/front/package.json b/front/package.json index e14fc9c53..70f494812 100644 --- a/front/package.json +++ b/front/package.json @@ -30,7 +30,6 @@ "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", @@ -45,6 +44,7 @@ "react-hook-form": "^7.45.1", "react-hotkeys-hook": "^4.4.0", "react-loading-skeleton": "^3.3.1", + "react-phone-number-input": "^3.3.4", "react-responsive": "^9.0.2", "react-router-dom": "^6.4.4", "react-select-event": "^5.5.1", diff --git a/front/src/modules/companies/__stories__/CompanyChip.stories.tsx b/front/src/modules/companies/__stories__/CompanyChip.stories.tsx index 12816ad3d..5488d8306 100644 --- a/front/src/modules/companies/__stories__/CompanyChip.stories.tsx +++ b/front/src/modules/companies/__stories__/CompanyChip.stories.tsx @@ -1,44 +1,18 @@ -import { BrowserRouter } from 'react-router-dom'; -import styled from '@emotion/styled'; import type { Meta, StoryObj } from '@storybook/react'; -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; +import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { CompanyChip } from '../components/CompanyChip'; const meta: Meta = { title: 'Modules/Companies/CompanyChip', component: CompanyChip, - decorators: [ - (Story) => ( - - - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRouterDecorator], }; export default meta; type Story = StoryObj; -const StyledTestCellContainer = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.primary}; - display: flex; - - height: fit-content; - justify-content: space-between; - - max-width: 250px; - - min-width: 250px; - - overflow: hidden; -`; - export const SmallName: Story = { args: { id: 'airbnb', diff --git a/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx index aca369732..14dfe2756 100644 --- a/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx +++ b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx @@ -1,35 +1,13 @@ -import { BrowserRouter } from 'react-router-dom'; -import styled from '@emotion/styled'; import type { Meta, StoryObj } from '@storybook/react'; -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; +import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { PersonChip } from '../PersonChip'; -const StyledTestCellContainer = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.primary}; - display: flex; - height: fit-content; - justify-content: space-between; - max-width: 250px; - min-width: 250px; - overflow: hidden; -`; - const meta: Meta = { title: 'Modules/People/PersonChip', component: PersonChip, - decorators: [ - (Story) => ( - - - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRouterDecorator], }; export default meta; diff --git a/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx b/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx index 469ccdd85..789e5859d 100644 --- a/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx +++ b/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx @@ -1,22 +1,14 @@ -import { BrowserRouter } from 'react-router-dom'; import type { Meta, StoryObj } from '@storybook/react'; import { IconPhone } from '@tabler/icons-react'; -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; +import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { PhoneEditableField } from '../PhoneEditableField'; const meta: Meta = { title: 'UI/EditableField/PhoneEditableField', component: PhoneEditableField, - decorators: [ - (Story) => ( - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRouterDecorator], argTypes: { icon: { type: 'boolean', 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 index af845aa56..a84153d2f 100644 --- a/front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx +++ b/front/src/modules/ui/input/email/components/__stories__/EmailInputDisplay.stories.tsx @@ -1,25 +1,13 @@ -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 { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { EmailInputDisplay } from '../EmailInputDisplay'; const meta: Meta = { - title: 'Modules/People/EmailInputDisplay', + title: 'UI/Input/EmailInputDisplay', component: EmailInputDisplay, - decorators: [ - (Story) => ( - - - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRouterDecorator], args: { value: 'mustajab.ikram@google.com', }, @@ -29,9 +17,4 @@ 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/__stories__/PhoneInputDisplay.stories.tsx b/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx index 9caa49737..f8ce5c787 100644 --- a/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx +++ b/front/src/modules/ui/input/phone/components/__stories__/PhoneInputDisplay.stories.tsx @@ -1,25 +1,13 @@ -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 { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { PhoneInputDisplay } from '../PhoneInputDisplay'; // Adjust the import path as needed const meta: Meta = { - title: 'Modules/People/PhoneInputDisplay', + title: 'UI/Input/PhoneInputDisplay', component: PhoneInputDisplay, - decorators: [ - (Story) => ( - - - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRouterDecorator], args: { value: '+33788901234', }, @@ -29,10 +17,4 @@ 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/table/editable-cell/type/components/PhoneCellEdit.tsx b/front/src/modules/ui/table/editable-cell/type/components/PhoneCellEdit.tsx index 23ae9ac9c..d0f66a66d 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/PhoneCellEdit.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/PhoneCellEdit.tsx @@ -1,13 +1,10 @@ -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; +import PhoneInput, { isPossiblePhoneNumber } from 'react-phone-number-input'; 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'; +import 'react-phone-number-input/style.css'; const StyledContainer = styled.div` align-items: center; @@ -17,113 +14,91 @@ const StyledContainer = styled.div` 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 = { +export type PhoneCellEditProps = { 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 StyledCustomPhoneInput = styled(PhoneInput)` + --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; + color: ${({ theme }) => theme.font.color.primary}; + + &::placeholder, + &::-webkit-input-placeholder { + color: ${({ theme }) => theme.font.color.light}; + font-family: ${({ theme }) => theme.font.family}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + } + + :focus { + outline: none; + } + } +`; + +export function PhoneCellEdit({ + autoFocus, + value, + onSubmit, +}: PhoneCellEditProps) { + const [internalValue, setInternalValue] = useState(value); + 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: [], - }); + if (isPossiblePhoneNumber(internalValue ?? '')) { + onSubmit(internalValue ?? ''); } - }, [value]); + } - useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel); + useRegisterCloseCellHandlers(wrapperRef, handleSubmit); return ( - ); diff --git a/front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx b/front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx similarity index 50% rename from front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx rename to front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx index c6e66ba1d..d7a223d36 100644 --- a/front/src/modules/people/components/__stories__/PhoneEditableField.stories.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/__stories__/PhoneCellEdit.stories.tsx @@ -1,24 +1,20 @@ -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'; +import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; +import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; const meta: Meta = { - title: 'Modules/People/EditableFields/PhoneEditableField', + title: 'UI/Table/EditableCell/PhoneCellEdit', component: PhoneCellEdit, - decorators: [ - (Story) => ( - - - - ), - ComponentDecorator, - ], + decorators: [ComponentWithRecoilScopeDecorator], args: { value: '+33714446494', autoFocus: true, }, + parameters: { + recoilScopeContext: TableRecoilScopeContext, + }, }; export default meta; diff --git a/front/src/pages/people/__stories__/People.inputs.stories.tsx b/front/src/pages/people/__stories__/People.inputs.stories.tsx index e0917fac4..f08626238 100644 --- a/front/src/pages/people/__stories__/People.inputs.stories.tsx +++ b/front/src/pages/people/__stories__/People.inputs.stories.tsx @@ -51,13 +51,21 @@ export const InteractWithManyRows: Story = { canvas.queryByTestId('editable-cell-edit-mode-container'), ).toBeNull(); - await userEvent.click(firstRowEmailCell); + if (!firstRowEmailCell.parentElement) { + throw new Error('No parent node'); + } + + await userEvent.click(firstRowEmailCell.parentElement); expect( canvas.queryByTestId('editable-cell-edit-mode-container'), ).toBeInTheDocument(); - await userEvent.click(secondRowEmailCell); + if (!secondRowEmailCell.parentElement) { + throw new Error('No parent node'); + } + + await userEvent.click(secondRowEmailCell.parentElement); await sleep(25); @@ -65,7 +73,7 @@ export const InteractWithManyRows: Story = { canvas.queryByTestId('editable-cell-edit-mode-container'), ).toBeNull(); - await userEvent.click(secondRowEmailCell); + await userEvent.click(secondRowEmailCell.parentElement); await sleep(25); diff --git a/front/yarn.lock b/front/yarn.lock index b70170250..40ce0cf94 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -8005,7 +8005,7 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -classnames@^2.2.6, classnames@^2.3.0: +classnames@^2.2.6, classnames@^2.3.0, classnames@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -8438,6 +8438,11 @@ cosmiconfig@^8.1.3, cosmiconfig@^8.2.0: parse-json "^5.0.0" path-type "^4.0.0" +country-flag-icons@^1.5.4: + version "1.5.7" + resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.7.tgz#f1f2ddf14f3cbf01cba6746374aeba94db35d4b4" + integrity sha512-AdvXhMcmSp7nBSkpGfW4qR/luAdRUutJqya9PuwRbsBzuoknThfultbv7Ib6fWsHXC43Es/4QJ8gzQQdBNm75A== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -11442,6 +11447,13 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +input-format@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/input-format/-/input-format-0.3.8.tgz#9445b0cab2f0457fbe36d77d607e942fd37345c5" + integrity sha512-tLR0XRig1xIcG1PtIpMd/uoltvkAI62CN9OIbtj4/tEJAkqTCQLNHUZ9N4M46w0dopny7Rlt/lRH5Xzp7e6F+g== + dependencies: + prop-types "^15.8.1" + inquirer@^8.0.0, inquirer@^8.2.0: version "8.2.6" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" @@ -11472,16 +11484,6 @@ 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" @@ -13216,7 +13218,7 @@ lib0@^0.2.42, lib0@^0.2.74: dependencies: isomorphic.js "^0.2.4" -libphonenumber-js@^1.10.26: +libphonenumber-js@^1.10.26, libphonenumber-js@^1.10.39: version "1.10.41" resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.41.tgz#14b6be5894bed3385808a6a088031b5b8a27c105" integrity sha512-4rmmF4u4vD3eGNuuCGjCPwRwO+fIuu1WWcS7VwbPTiMFkJd8F02v8o5pY5tlYuMR+xOvJ88mtOHpkm0Tnu2LcQ== @@ -16127,6 +16129,17 @@ react-onclickoutside@^6.12.2: resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc" integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A== +react-phone-number-input@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.3.4.tgz#a9efaa70185444306bef296c538ecfbbadc0b4b3" + integrity sha512-g3cnJroqkgPlfg2jWg1I7TRRASkTutYVdTbmi8XObhmAv3OCdJHZxaeW5nKelPlvqyWHXnTKZNE3lKMas3rS0Q== + dependencies: + classnames "^2.3.1" + country-flag-icons "^1.5.4" + input-format "^0.3.8" + libphonenumber-js "^1.10.39" + prop-types "^15.8.1" + react-popper@^2.2.5, react-popper@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"