diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountryCodeSelectInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountryCodeSelectInput.tsx index f225d0785..6fc83073c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountryCodeSelectInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountryCodeSelectInput.tsx @@ -57,7 +57,9 @@ export const FormCountryCodeSelectInput = ({ onPersist={onChange} options={options} defaultValue={selectedCountryCode} + readonly={readonly} VariablePicker={VariablePicker} + preventDisplayPadding /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountrySelectInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountrySelectInput.tsx index 866ac1da2..3e1dd15d3 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountrySelectInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCountrySelectInput.tsx @@ -57,7 +57,9 @@ export const FormCountrySelectInput = ({ onPersist={onChange} options={options} defaultValue={selectedCountryName} + readonly={readonly} VariablePicker={VariablePicker} + preventDisplayPadding /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCurrencyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCurrencyFieldInput.tsx index d2536ff37..06cc9a2b5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCurrencyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormCurrencyFieldInput.tsx @@ -62,6 +62,7 @@ export const FormCurrencyFieldInput = ({ clearLabel={'Currency Code'} VariablePicker={VariablePicker} readonly={readonly} + preventDisplayPadding /> diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx index 851b7c561..e5d944f7b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx @@ -66,19 +66,19 @@ export const FormRawJsonFieldInput = ({ - {VariablePicker ? ( + {VariablePicker && !readonly && ( - ) : null} + )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx index 437b342fb..37b42aad4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx @@ -28,6 +28,7 @@ type FormSelectFieldInputProps = { options: SelectOption[]; clearLabel?: string; readonly?: boolean; + preventDisplayPadding?: boolean; }; const StyledDisplayModeReadonlyContainer = styled.div` @@ -65,6 +66,7 @@ export const FormSelectFieldInput = ({ options, clearLabel, readonly, + preventDisplayPadding, }: FormSelectFieldInputProps) => { const inputId = useId(); @@ -219,7 +221,7 @@ export const FormSelectFieldInput = ({ {draftValue.type === 'static' ? ( readonly ? ( @@ -229,6 +231,7 @@ export const FormSelectFieldInput = ({ color={selectedOption.color ?? 'transparent'} label={selectedOption.label} Icon={selectedOption.icon ?? undefined} + preventPadding={preventDisplayPadding} /> )} )} { const inputId = useId(); @@ -94,7 +95,7 @@ export const FormUuidFieldInput = ({ {draftValue.type === 'static' ? ( ) : ( )} - {VariablePicker ? ( + {VariablePicker && !readonly ? ( = { @@ -21,7 +21,7 @@ export const Default: Story = { addressStreet2: 'Apt 123', addressCity: 'Springfield', addressState: 'IL', - addressCountry: 'US', + addressCountry: 'United States', addressPostcode: '12345', addressLat: 39.781721, addressLng: -89.650148, @@ -35,3 +35,85 @@ export const Default: Story = { await canvas.findByText('Post Code'); }, }; + +export const WithVariables: Story = { + args: { + label: 'Address', + defaultValue: { + addressStreet1: '{{a.street1}}', + addressStreet2: '{{a.street2}}', + addressCity: '{{a.city}}', + addressState: '{{a.state}}', + addressCountry: '{{a.country}}', + addressPostcode: '{{a.postcode}}', + addressLat: 39.781721, + addressLng: -89.650148, + }, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const street1Variable = await canvas.findByText('street1'); + const street2Variable = await canvas.findByText('street2'); + const cityVariable = await canvas.findByText('city'); + const stateVariable = await canvas.findByText('state'); + const countryVariable = await canvas.findByText('country'); + const postcodeVariable = await canvas.findByText('postcode'); + + expect(street1Variable).toBeVisible(); + expect(street2Variable).toBeVisible(); + expect(cityVariable).toBeVisible(); + expect(stateVariable).toBeVisible(); + expect(countryVariable).toBeVisible(); + expect(postcodeVariable).toBeVisible(); + + const variablePickers = await canvas.findAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(6); + }, +}; + +export const Disabled: Story = { + args: { + label: 'Address', + readonly: true, + defaultValue: { + addressStreet1: '123 Main St', + addressStreet2: 'Apt 123', + addressCity: 'Springfield', + addressState: 'IL', + addressCountry: 'United States', + addressPostcode: '12345', + addressLat: 39.781721, + addressLng: -89.650148, + }, + onPersist: fn(), + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const street1Input = await canvas.findByText('123 Main St'); + const street2Input = await canvas.findByText('Apt 123'); + const cityInput = await canvas.findByText('Springfield'); + const stateInput = await canvas.findByText('IL'); + const postcodeInput = await canvas.findByText('12345'); + const countrySelect = await canvas.findByText('United States'); + + await userEvent.type(street1Input, 'XXX'); + await userEvent.type(street2Input, 'YYY'); + await userEvent.type(cityInput, 'ZZZ'); + await userEvent.type(stateInput, 'ZZ'); + await userEvent.type(postcodeInput, '1234'); + + await userEvent.click(countrySelect); + + const searchInputInModal = canvas.queryByPlaceholderText('Search'); + expect(searchInputInModal).not.toBeInTheDocument(); + + expect(args.onPersist).not.toHaveBeenCalled(); + + const variablePickers = canvas.queryAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(0); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormEmailsFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormEmailsFieldInput.stories.tsx index dbf2ef332..249e269e4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormEmailsFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormEmailsFieldInput.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; +import { expect, fn, userEvent, within } from '@storybook/test'; import { FormEmailsFieldInput } from '../FormEmailsFieldInput'; const meta: Meta = { @@ -29,3 +29,54 @@ export const Default: Story = { await canvas.findByText('tim@twenty.com'); }, }; + +export const WithVariable: Story = { + args: { + label: 'Emails', + defaultValue: { + primaryEmail: '{{a.b.c}}', + additionalEmails: [], + }, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const primaryEmailVariable = await canvas.findByText('c'); + expect(primaryEmailVariable).toBeVisible(); + + const variablePicker = await canvas.findByText('VariablePicker'); + expect(variablePicker).toBeVisible(); + }, +}; + +export const Disabled: Story = { + args: { + label: 'Emails', + defaultValue: { + primaryEmail: 'tim@twenty.com', + additionalEmails: [], + }, + onPersist: fn(), + VariablePicker: () =>
VariablePicker
, + readonly: true, + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const editor = canvasElement.querySelector('.ProseMirror > p'); + expect(editor).toBeVisible(); + + const defaultValue = await canvas.findByText('tim@twenty.com'); + expect(defaultValue).toBeVisible(); + + await userEvent.type(editor, 'hello@gmail.com'); + + expect(args.onPersist).not.toHaveBeenCalled(); + expect(canvas.queryByText('hello@gmail.com')).not.toBeInTheDocument(); + expect(defaultValue).toBeVisible(); + + const variablePicker = canvas.queryByText('VariablePicker'); + expect(variablePicker).not.toBeInTheDocument(); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormFullNameFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormFullNameFieldInput.stories.tsx index 62def51e5..8ff853e79 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormFullNameFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormFullNameFieldInput.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; +import { expect, fn, userEvent, within } from '@storybook/test'; import { FormFullNameFieldInput } from '../FormFullNameFieldInput'; const meta: Meta = { @@ -29,3 +29,53 @@ export const Default: Story = { await canvas.findByText('Last Name'); }, }; + +export const WithVariable: Story = { + args: { + label: 'Name', + defaultValue: { + firstName: '{{a.firstName}}', + lastName: '{{a.lastName}}', + }, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const firstNameVariable = await canvas.findByText('firstName'); + expect(firstNameVariable).toBeVisible(); + + const lastNameVariable = await canvas.findByText('lastName'); + expect(lastNameVariable).toBeVisible(); + + const variablePickers = await canvas.findAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(2); + }, +}; + +export const Disabled: Story = { + args: { + label: 'Name', + readonly: true, + defaultValue: { + firstName: 'John', + lastName: 'Doe', + }, + VariablePicker: () =>
VariablePicker
, + onPersist: fn(), + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const firstNameVariable = await canvas.findByText('John'); + const lastNameVariable = await canvas.findByText('Doe'); + + await userEvent.type(firstNameVariable, 'Jane'); + await userEvent.type(lastNameVariable, 'Smith'); + + expect(args.onPersist).not.toHaveBeenCalled(); + + const variablePickers = canvas.queryAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(0); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormLinksFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormLinksFieldInput.stories.tsx index 8dccf506c..7428297b0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormLinksFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormLinksFieldInput.stories.tsx @@ -1,5 +1,6 @@ +import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; +import { fn, userEvent, within } from '@storybook/test'; import { FormLinksFieldInput } from '../FormLinksFieldInput'; const meta: Meta = { @@ -29,3 +30,57 @@ export const Default: Story = { await canvas.findByText('Google'); }, }; + +export const WithVariables: Story = { + args: { + label: 'Domain Name', + defaultValue: { + primaryLinkLabel: '{{a.label}}', + primaryLinkUrl: '{{a.url}}', + }, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const primaryLinkLabelVariable = await canvas.findByText('label'); + expect(primaryLinkLabelVariable).toBeVisible(); + + const primaryLinkUrlVariable = await canvas.findByText('url'); + expect(primaryLinkUrlVariable).toBeVisible(); + + const variablePickers = await canvas.findAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(2); + + for (const variablePicker of variablePickers) { + expect(variablePicker).toBeVisible(); + } + }, +}; + +export const Disabled: Story = { + args: { + label: 'Number field...', + readonly: true, + onPersist: fn(), + VariablePicker: () =>
VariablePicker
, + defaultValue: { + primaryLinkLabel: 'Google', + primaryLinkUrl: 'https://www.google.com', + }, + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const labelInput = await canvas.findByText('Google'); + const linkInput = await canvas.findByText('https://www.google.com'); + + await userEvent.type(labelInput, 'Yahoo'); + await userEvent.type(linkInput, 'https://www.yahoo.com'); + + expect(args.onPersist).not.toHaveBeenCalled(); + + const variablePickers = canvas.queryAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(0); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx index eec784fa0..64ab42c1e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; +import { expect, userEvent, within } from '@storybook/test'; import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata'; import { FormPhoneFieldInput } from '../FormPhoneFieldInput'; @@ -32,3 +32,57 @@ export const Default: Story = { await canvas.findByText('Phone'); }, }; + +export const WithVariables: Story = { + args: { + label: 'Enter phone...', + defaultValue: { + primaryPhoneCountryCode: '{{a.countryCode}}', + primaryPhoneNumber: '{{a.phoneNumber}}', + }, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const countryCodeVariable = await canvas.findByText('countryCode'); + expect(countryCodeVariable).toBeVisible(); + + const phoneNumberVariable = await canvas.findByText('phoneNumber'); + expect(phoneNumberVariable).toBeVisible(); + + const variablePickers = await canvas.findAllByText('VariablePicker'); + + expect(variablePickers).toHaveLength(2); + + for (const variablePicker of variablePickers) { + expect(variablePicker).toBeVisible(); + } + }, +}; + +export const Disabled: Story = { + args: { + label: 'Enter phone...', + readonly: true, + VariablePicker: () =>
VariablePicker
, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const countryInput = await canvas.findByText('No country'); + expect(countryInput).toBeVisible(); + + await userEvent.click(countryInput); + + const searchInputInModal = canvas.queryByPlaceholderText('Search'); + expect(searchInputInModal).not.toBeInTheDocument(); + + const phoneNumberInput = + await canvas.findByPlaceholderText('Enter phone number'); + expect(phoneNumberInput).toBeDisabled(); + + const variablePickers = canvas.queryAllByText('VariablePicker'); + expect(variablePickers).toHaveLength(0); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormRawJsonFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormRawJsonFieldInput.stories.tsx index 70bd7fd5f..48fad9f9a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormRawJsonFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormRawJsonFieldInput.stories.tsx @@ -32,8 +32,21 @@ export const Readonly: Story = { placeholder: 'Enter valid json', readonly: true, onPersist: fn(), + VariablePicker: ({ onVariableSelect }) => { + return ( + + ); + }, }, play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + const editor = canvasElement.querySelector('.ProseMirror > p'); expect(editor).toBeVisible(); @@ -46,6 +59,9 @@ export const Readonly: Story = { }); expect(args.onPersist).not.toHaveBeenCalled(); + + const addVariableButton = canvas.queryByText('Add variable'); + expect(addVariableButton).not.toBeInTheDocument(); }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormUuidFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormUuidFieldInput.stories.tsx index 524eff315..3c65bfc04 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormUuidFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormUuidFieldInput.stories.tsx @@ -212,3 +212,33 @@ export const ReplaceStaticValueWithVariable: Story = { ]); }, }; + +export const Disabled: Story = { + args: { + label: 'UUID field', + placeholder: 'Enter UUID', + readonly: true, + VariablePicker: ({ onVariableSelect }) => { + return ( + + ); + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const input = await canvas.findByPlaceholderText('Enter UUID'); + + expect(input).toBeDisabled(); + + const variablePicker = canvas.queryByText('Add variable'); + + expect(variablePicker).not.toBeInTheDocument(); + }, +}; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/SelectDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/SelectDisplay.tsx index 13ac37aa3..73820255a 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/SelectDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/SelectDisplay.tsx @@ -4,8 +4,22 @@ type SelectDisplayProps = { color: ThemeColor | 'transparent'; label: string; Icon?: IconComponent; + preventPadding?: boolean; }; -export const SelectDisplay = ({ color, label, Icon }: SelectDisplayProps) => { - return ; +export const SelectDisplay = ({ + color, + label, + Icon, + preventPadding, +}: SelectDisplayProps) => { + return ( + + ); };