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 (
+
+ );
};