Skip to content

Commit

Permalink
feat(Form fields): allow blocking copy/cut (#1251)
Browse files Browse the repository at this point in the history
  • Loading branch information
atabel authored Sep 26, 2024
1 parent e35a99e commit 8fd2838
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 1 deletion.
5 changes: 4 additions & 1 deletion doc/theme-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ type ThemeConfig = {
insideNovumNativeApp?: boolean;
userAgent?: string;
};
texts?: ThemeTexts;
texts?: Partial<Dictionary>;
analytics?: {
logEvent: (event: TrackingEvent) => Promise<void>;
};
Link?: LinkComponent;
useHrefDecorator: () => (href: string) => string;
preventCopyInFormFields?: boolean;
};
```

Expand Down Expand Up @@ -56,6 +57,8 @@ Here is a description of every attribute:
- `useHrefDecorator`: it is a React hook that a function that takes a `href` and returns a new `href`. This is
useful to automatically add parameters to the `href` being used in Touchable components (for example, to add
a `utm_source` parameter to the `href`).
- `preventCopyInFormFields?`: this is used as the default value for `preventCopy` prop in form fields. `false`
by default.

## LinkComponent

Expand Down
53 changes: 53 additions & 0 deletions src/__acceptance_tests__/input-fields-acceptance-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,56 @@ test('PinField focus management', async () => {
await firstDigitField.press('Delete');
await screen.findByText("value: (string) '2'");
}, 1200000);

const copyFieldContentToClipboard = async (page: PageApi, field: ElementHandle) => {
await field.focus();
await page.keyboard.down('ControlLeft');
await page.keyboard.press('KeyA');
await page.keyboard.up('ControlLeft');

await page.keyboard.down('ControlLeft');
await page.keyboard.press('KeyC');
await page.keyboard.up('ControlLeft');
};

const pasteClipboardContentToField = async (page: PageApi, field: ElementHandle) => {
await field.focus();
await page.keyboard.down('ControlLeft');
await page.keyboard.press('KeyV');
await page.keyboard.up('ControlLeft');
};

test('TextField preventCopy', async () => {
let page = await openStoryPage(getStoryOfType('textfield', 'controlled'));

let field = await screen.findByLabelText('Label');

await clearAndType(page, field, 'first input text');

await copyFieldContentToClipboard(page, field);
await page.clear(field);
expect(await getValue(field)).toBe('');

await pasteClipboardContentToField(page, field);

// paste worked because preventCopy is false
expect(await getValue(field)).toBe('first input text');

page = await openStoryPage({
...getStoryOfType('textfield', 'controlled'),
args: {preventCopy: true},
});

field = await screen.findByLabelText('Label');

await clearAndType(page, field, 'second input text');

await copyFieldContentToClipboard(page, field);
await page.clear(field);
expect(await getValue(field)).toBe('');

await pasteClipboardContentToField(page, field);

// paste did not work because preventCopy is true. So the field has the previous clipboard content
expect(await getValue(field)).toBe('first input text');
}, 1200000);
2 changes: 2 additions & 0 deletions src/__stories__/credit-card-expiration-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface CreditCardExpirationFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: CreditCardExpirationFieldBaseArgs = {
Expand All @@ -25,6 +26,7 @@ const defaultBaseArgs: CreditCardExpirationFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface CreditCardExpirationFieldControlledArgs extends CreditCardExpirationFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/credit-card-number-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface CreditCardNumberFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: CreditCardNumberFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: CreditCardNumberFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface CreditCardNumberFieldControlledArgs extends CreditCardNumberFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/cvv-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface CvvFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: CvvFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: CvvFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface CvvFieldControlledArgs extends CvvFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/date-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface DateFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
min: boolean;
max: boolean;
}
Expand All @@ -28,6 +29,7 @@ const defaultBaseArgs: DateFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
min: false,
max: false,
};
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/date-time-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface DateTimeFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
min: boolean;
max: boolean;
}
Expand All @@ -28,6 +29,7 @@ const defaultBaseArgs: DateTimeFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
min: false,
max: false,
};
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/decimal-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface DecimalFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: DecimalFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: DecimalFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface DecimalFieldControlledArgs extends DecimalFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/email-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface EmailFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: EmailFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: EmailFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface EmailFieldControlledArgs extends EmailFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/iban-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface IbanFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: IbanFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: IbanFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface IbanFieldControlledArgs extends IbanFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/integer-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface IntegerFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: IntegerFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: IntegerFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface IntegerFieldControlledArgs extends IntegerFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/month-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface MonthFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
min: boolean;
max: boolean;
}
Expand All @@ -28,6 +29,7 @@ const defaultBaseArgs: MonthFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
min: false,
max: false,
};
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/password-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface PasswordFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: PasswordFieldBaseArgs = {
Expand All @@ -27,6 +28,7 @@ const defaultBaseArgs: PasswordFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface PasswordFieldControlledArgs extends PasswordFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/phone-number-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface PhoneNumberFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
e164: boolean;
}

Expand All @@ -36,6 +37,7 @@ const defaultBaseArgs: PhoneNumberFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
e164: true,
};

Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/search-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface SearchFieldBaseArgs {
optional: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: SearchFieldBaseArgs = {
Expand All @@ -33,6 +34,7 @@ const defaultBaseArgs: SearchFieldBaseArgs = {
optional: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface SearchFieldControlledArgs extends SearchFieldBaseArgs {
Expand Down
2 changes: 2 additions & 0 deletions src/__stories__/text-field-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface TextFieldBaseArgs {
icon: boolean;
disabled: boolean;
readOnly: boolean;
preventCopy: boolean;
}

const defaultBaseArgs: TextFieldBaseArgs = {
Expand All @@ -43,6 +44,7 @@ const defaultBaseArgs: TextFieldBaseArgs = {
icon: false,
disabled: false,
readOnly: false,
preventCopy: false,
};

interface TextFieldControlledArgs extends TextFieldBaseArgs {
Expand Down
13 changes: 13 additions & 0 deletions src/text-field-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export interface CommonFormFieldProps<T = HTMLInputElement> {
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
children?: void;
readOnly?: boolean;
preventCopy?: boolean;
dataAttributes?: DataAttributes;
}

Expand Down Expand Up @@ -174,12 +175,17 @@ interface TextFieldBaseProps {
multiline?: boolean;
inputMode?: string;
readOnly?: boolean;
preventCopy?: boolean;
min?: string;
max?: string;
role?: string;
dataAttributes?: DataAttributes;
}

const preventCopyHandler = (e: React.ClipboardEvent<HTMLInputElement>) => {
e.preventDefault();
};

export const TextFieldBase = React.forwardRef<any, TextFieldBaseProps>(
(
{
Expand All @@ -206,10 +212,13 @@ export const TextFieldBase = React.forwardRef<any, TextFieldBaseProps>(
autoComplete: autoCompleteProp,
fullWidth,
dataAttributes,
preventCopy,
...rest
},
ref
) => {
const {preventCopyInFormFields} = useTheme();
preventCopy = preventCopy ?? preventCopyInFormFields;
const reactId = React.useId();
const id = idProp || reactId;
const helperTextid = React.useId();
Expand Down Expand Up @@ -419,6 +428,10 @@ export const TextFieldBase = React.forwardRef<any, TextFieldBaseProps>(
value,
...(error && {'aria-invalid': true}),
...(helperText && {'aria-describedby': helperTextid}),
...(preventCopy && {
onCopy: preventCopyHandler,
onCut: preventCopyHandler,
}),
})}
</Text3>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/theme-context-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ const ThemeContextProvider = ({theme, children, as, withoutStyles = false}: Prop
isIos: getPlatform(platformOverrides) === 'ios',
useHrefDecorator: theme.useHrefDecorator ?? useDefaultHrefDecorator,
t: translate,
preventCopyInFormFields: theme.preventCopyInFormFields ?? false,
};
}, [colors, theme, isDarkModeEnabled, translate]);

Expand Down
2 changes: 2 additions & 0 deletions src/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export type ThemeConfig = Readonly<{
};
useHrefDecorator?: () => (href: string) => string;
enableTabFocus?: boolean;
preventCopyInFormFields?: boolean;
}>;

// This is the lib INTERNAL context
Expand Down Expand Up @@ -167,4 +168,5 @@ export type Theme = {
isIos: boolean;
useHrefDecorator: () => (href: string) => string;
t: (token: TextToken) => string;
preventCopyInFormFields: boolean;
};

1 comment on commit 8fd2838

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for mistica-web ready!

✅ Preview
https://mistica-c0htc871x-flows-projects-65bb050e.vercel.app

Built with commit 8fd2838.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.