Add readonly mode to form fields - 2nd part (#9582)
In this PR, I implemented or confirmed that the read-only mode works for the following fields: - [x] FormUuidFieldInput - [x] FormRawJsonFieldInput - [x] FormPhoneFieldInput - [x] FormEmailsFieldInput - [x] FormLinksFieldInput - [x] FormAddressFieldInput - [x] FormFullNameFieldInput
This commit is contained in:
committed by
GitHub
parent
21a6dff2c9
commit
5eeee6a7ed
@ -57,7 +57,9 @@ export const FormCountryCodeSelectInput = ({
|
||||
onPersist={onChange}
|
||||
options={options}
|
||||
defaultValue={selectedCountryCode}
|
||||
readonly={readonly}
|
||||
VariablePicker={VariablePicker}
|
||||
preventDisplayPadding
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -57,7 +57,9 @@ export const FormCountrySelectInput = ({
|
||||
onPersist={onChange}
|
||||
options={options}
|
||||
defaultValue={selectedCountryName}
|
||||
readonly={readonly}
|
||||
VariablePicker={VariablePicker}
|
||||
preventDisplayPadding
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -62,6 +62,7 @@ export const FormCurrencyFieldInput = ({
|
||||
clearLabel={'Currency Code'}
|
||||
VariablePicker={VariablePicker}
|
||||
readonly={readonly}
|
||||
preventDisplayPadding
|
||||
/>
|
||||
<FormNumberFieldInput
|
||||
label="Amount Micros"
|
||||
|
||||
@ -57,6 +57,7 @@ export const FormPhoneFieldInput = ({
|
||||
VariablePicker={VariablePicker}
|
||||
placeholder="Enter phone number"
|
||||
hint="Without calling code"
|
||||
readonly={readonly}
|
||||
/>
|
||||
</FormNestedFieldInputContainer>
|
||||
</FormFieldInputContainer>
|
||||
|
||||
@ -66,19 +66,19 @@ export const FormRawJsonFieldInput = ({
|
||||
|
||||
<FormFieldInputRowContainer multiline>
|
||||
<FormFieldInputInputContainer
|
||||
hasRightElement={isDefined(VariablePicker)}
|
||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||
multiline
|
||||
>
|
||||
<TextVariableEditor editor={editor} multiline readonly={readonly} />
|
||||
</FormFieldInputInputContainer>
|
||||
|
||||
{VariablePicker ? (
|
||||
{VariablePicker && !readonly && (
|
||||
<VariablePicker
|
||||
inputId={inputId}
|
||||
multiline
|
||||
onVariableSelect={handleVariableTagInsert}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</FormFieldInputRowContainer>
|
||||
</FormFieldInputContainer>
|
||||
);
|
||||
|
||||
@ -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 = ({
|
||||
|
||||
<FormFieldInputRowContainer>
|
||||
<FormFieldInputInputContainer
|
||||
hasRightElement={isDefined(VariablePicker)}
|
||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||
>
|
||||
{draftValue.type === 'static' ? (
|
||||
readonly ? (
|
||||
@ -229,6 +231,7 @@ export const FormSelectFieldInput = ({
|
||||
color={selectedOption.color ?? 'transparent'}
|
||||
label={selectedOption.label}
|
||||
Icon={selectedOption.icon ?? undefined}
|
||||
preventPadding={preventDisplayPadding}
|
||||
/>
|
||||
)}
|
||||
<IconChevronDown
|
||||
@ -248,6 +251,7 @@ export const FormSelectFieldInput = ({
|
||||
color={selectedOption.color ?? 'transparent'}
|
||||
label={selectedOption.label}
|
||||
Icon={selectedOption.icon ?? undefined}
|
||||
preventPadding={preventDisplayPadding}
|
||||
/>
|
||||
)}
|
||||
<IconChevronDown
|
||||
|
||||
@ -28,6 +28,7 @@ export const FormUuidFieldInput = ({
|
||||
defaultValue,
|
||||
placeholder,
|
||||
onPersist,
|
||||
readonly,
|
||||
VariablePicker,
|
||||
}: FormUuidFieldInputProps) => {
|
||||
const inputId = useId();
|
||||
@ -94,7 +95,7 @@ export const FormUuidFieldInput = ({
|
||||
|
||||
<FormFieldInputRowContainer>
|
||||
<FormFieldInputInputContainer
|
||||
hasRightElement={isDefined(VariablePicker)}
|
||||
hasRightElement={isDefined(VariablePicker) && !readonly}
|
||||
>
|
||||
{draftValue.type === 'static' ? (
|
||||
<StyledInput
|
||||
@ -103,17 +104,18 @@ export const FormUuidFieldInput = ({
|
||||
value={draftValue.value}
|
||||
copyButton={false}
|
||||
hotkeyScope="record-create"
|
||||
disabled={readonly}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
) : (
|
||||
<VariableChip
|
||||
rawVariableName={draftValue.value}
|
||||
onRemove={handleUnlinkVariable}
|
||||
onRemove={readonly ? undefined : handleUnlinkVariable}
|
||||
/>
|
||||
)}
|
||||
</FormFieldInputInputContainer>
|
||||
|
||||
{VariablePicker ? (
|
||||
{VariablePicker && !readonly ? (
|
||||
<VariablePicker
|
||||
inputId={inputId}
|
||||
onVariableSelect={handleVariableTagInsert}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { expect, fn, userEvent, within } from '@storybook/test';
|
||||
import { FormAddressFieldInput } from '../FormAddressFieldInput';
|
||||
|
||||
const meta: Meta<typeof FormAddressFieldInput> = {
|
||||
@ -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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@ -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<typeof FormEmailsFieldInput> = {
|
||||
@ -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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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: () => <div>VariablePicker</div>,
|
||||
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();
|
||||
},
|
||||
};
|
||||
|
||||
@ -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<typeof FormFullNameFieldInput> = {
|
||||
@ -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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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: () => <div>VariablePicker</div>,
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@ -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<typeof FormLinksFieldInput> = {
|
||||
@ -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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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: () => <div>VariablePicker</div>,
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@ -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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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: () => <div>VariablePicker</div>,
|
||||
},
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@ -32,8 +32,21 @@ export const Readonly: Story = {
|
||||
placeholder: 'Enter valid json',
|
||||
readonly: true,
|
||||
onPersist: fn(),
|
||||
VariablePicker: ({ onVariableSelect }) => {
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
onVariableSelect('{{test}}');
|
||||
}}
|
||||
>
|
||||
Add variable
|
||||
</button>
|
||||
);
|
||||
},
|
||||
},
|
||||
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();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -212,3 +212,33 @@ export const ReplaceStaticValueWithVariable: Story = {
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
label: 'UUID field',
|
||||
placeholder: 'Enter UUID',
|
||||
readonly: true,
|
||||
VariablePicker: ({ onVariableSelect }) => {
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
onVariableSelect('{{test}}');
|
||||
}}
|
||||
>
|
||||
Add variable
|
||||
</button>
|
||||
);
|
||||
},
|
||||
},
|
||||
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();
|
||||
},
|
||||
};
|
||||
|
||||
@ -4,8 +4,22 @@ type SelectDisplayProps = {
|
||||
color: ThemeColor | 'transparent';
|
||||
label: string;
|
||||
Icon?: IconComponent;
|
||||
preventPadding?: boolean;
|
||||
};
|
||||
|
||||
export const SelectDisplay = ({ color, label, Icon }: SelectDisplayProps) => {
|
||||
return <Tag preventShrink color={color} text={label} Icon={Icon} />;
|
||||
export const SelectDisplay = ({
|
||||
color,
|
||||
label,
|
||||
Icon,
|
||||
preventPadding,
|
||||
}: SelectDisplayProps) => {
|
||||
return (
|
||||
<Tag
|
||||
preventShrink
|
||||
color={color}
|
||||
text={label}
|
||||
Icon={Icon}
|
||||
preventPadding={preventPadding}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user