From 38a6085e0bb534ea1621d9a4d4c356a461887d1d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 14 Feb 2025 13:47:36 +0200 Subject: [PATCH 01/17] Save form state for unsubmitted data --- .../core/src/js/feedback/FeedbackForm.tsx | 45 +++++++++++++++---- .../core/test/feedback/FeedbackForm.test.tsx | 30 +++++++++++++ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index b6aac2f412..8f5103854c 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -33,6 +33,15 @@ export class FeedbackForm extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }); + this.setState({ filename, attachment: data }, this._saveFormState); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -142,11 +154,11 @@ export class FeedbackForm extends React.Component { - this.setState({ filename, attachment: attachement }); + this.setState({ filename, attachment: attachement }, this._saveFormState); }); } } else { - this.setState({ filename: undefined, attachment: undefined }); + this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); } } @@ -199,7 +211,7 @@ export class FeedbackForm extends React.Component this.setState({ name: value })} + onChangeText={(value) => this.setState({ name: value }, this._saveFormState)} /> )} @@ -215,7 +227,7 @@ export class FeedbackForm extends React.Component this.setState({ email: value })} + onChangeText={(value) => this.setState({ email: value }, this._saveFormState)} /> )} @@ -228,7 +240,7 @@ export class FeedbackForm extends React.Component this.setState({ description: value })} + onChangeText={(value) => this.setState({ description: value }, this._saveFormState)} multiline /> {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( @@ -254,4 +266,19 @@ export class FeedbackForm extends React.Component ); } + + private _saveFormState = (): void => { + FeedbackForm._savedState = { ...this.state }; + }; + + private _clearFormState = (): void => { + FeedbackForm._savedState = { + isVisible: false, + name: '', + email: '', + description: '', + filename: undefined, + attachment: undefined, + }; + }; } diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx index 92ed48fe4a..15918799ca 100644 --- a/packages/core/test/feedback/FeedbackForm.test.tsx +++ b/packages/core/test/feedback/FeedbackForm.test.tsx @@ -354,4 +354,34 @@ describe('FeedbackForm', () => { expect(mockOnFormClose).toHaveBeenCalled(); }); + + it('onCancel the input is saved and restored when the form reopens', async () => { + const { getByPlaceholderText, getByText } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + fireEvent.press(getByText(defaultProps.cancelButtonLabel)); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.'); + }); + + it('onSubmit the saved input is cleared and not restored when the form reopens', async () => { + const { getByPlaceholderText, getByText } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + fireEvent.press(getByText(defaultProps.submitButtonLabel)); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('Test User'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('test@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe(''); + }); }); From 56a113e15e7f1c25af6b982956b7e08ea7169d95 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 14 Feb 2025 17:55:15 +0200 Subject: [PATCH 02/17] Show selected screenshot --- .../src/js/feedback/FeedbackForm.styles.ts | 14 +- .../core/src/js/feedback/FeedbackForm.tsx | 32 ++- .../src/js/feedback/FeedbackForm.types.ts | 3 + .../__snapshots__/FeedbackForm.test.tsx.snap | 245 ++++++++++-------- 4 files changed, 177 insertions(+), 117 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts index 1e90af07c6..e1373f6c8c 100644 --- a/packages/core/src/js/feedback/FeedbackForm.styles.ts +++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts @@ -45,8 +45,20 @@ const defaultStyles: FeedbackFormStyles = { backgroundColor: '#eee', padding: 15, borderRadius: 5, - marginBottom: 20, alignItems: 'center', + flex: 1, + }, + screenshotContainer: { + flexDirection: 'row', + alignItems: 'center', + width: '100%', + marginBottom: 20, + }, + screenshotThumbnail: { + width: 50, + height: 50, + borderRadius: 5, + marginRight: 10, }, screenshotText: { color: '#333', diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 8f5103854c..19a3a96f8d 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -40,6 +40,7 @@ export class FeedbackForm extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }, this._saveFormState); + this.setState({ filename, attachment: data, attachmentUri: imageUri }, this._saveFormState); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -154,11 +156,12 @@ export class FeedbackForm extends React.Component { - this.setState({ filename, attachment: attachement }, this._saveFormState); + // TODO: Add support for image uri when using onAddScreenshot + this.setState({ filename, attachment: attachement, attachmentUri: undefined }, this._saveFormState); }); } } else { - this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); + this.setState({ filename: undefined, attachment: undefined, attachmentUri: undefined }, this._saveFormState); } } @@ -244,13 +247,21 @@ export class FeedbackForm extends React.Component {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( - - - {!this.state.filename && !this.state.attachment - ? text.addScreenshotButtonLabel - : text.removeScreenshotButtonLabel} - - + + {this.state.attachmentUri && ( + + )} + + + {!this.state.filename && !this.state.attachment + ? text.addScreenshotButtonLabel + : text.removeScreenshotButtonLabel} + + + )} {text.submitButtonLabel} @@ -279,6 +290,7 @@ export class FeedbackForm extends React.Component - - Add a screenshot - + + Add a screenshot + + - - Add Screenshot - + + Add Screenshot + + - - Add a screenshot - + + Add a screenshot + + Date: Fri, 14 Feb 2025 18:09:03 +0200 Subject: [PATCH 03/17] Use image uri instead of UInt8Array in onAddScreenshot callback --- packages/core/src/js/feedback/FeedbackForm.tsx | 14 +++++++++++--- .../core/src/js/feedback/FeedbackForm.types.ts | 2 +- packages/core/src/js/feedback/defaults.ts | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 19a3a96f8d..ae7ce25088 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -155,9 +155,17 @@ export class FeedbackForm extends React.Component { - // TODO: Add support for image uri when using onAddScreenshot - this.setState({ filename, attachment: attachement, attachmentUri: undefined }, this._saveFormState); + onAddScreenshot((filename: string, fileUri: string) => { + NATIVE.getDataFromUri(fileUri).then((data) => { + if (data != null) { + this.setState({ filename, attachment: data, attachmentUri: fileUri }, this._saveFormState); + } else { + logger.error('Failed to read image data from uri:', fileUri); + } + }) + .catch((error) => { + logger.error('Failed to read image data from uri:', fileUri, 'error: ', error); + }); }); } } else { diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts index e5ee456f7e..bb53e5b8df 100644 --- a/packages/core/src/js/feedback/FeedbackForm.types.ts +++ b/packages/core/src/js/feedback/FeedbackForm.types.ts @@ -171,7 +171,7 @@ export interface FeedbackCallbacks { /** * Callback when a screenshot is added */ - onAddScreenshot?: (attachFile: (filename: string, data: Uint8Array) => void) => void; + onAddScreenshot?: (attachFile: (filename: string, fileUri: string) => void) => void; /** * Callback when feedback is successfully submitted diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index a1d8e74b62..af4311b072 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -33,7 +33,7 @@ export const defaultConfiguration: Partial = { ); } }, - onAddScreenshot: (_: (filename: string, data: Uint8Array) => void) => { + onAddScreenshot: (_: (filename: string, fileUri: string) => void) => { if (__DEV__) { Alert.alert('Development note', 'onAddScreenshot callback is not implemented.'); } From 1efb23e290f3aeef6977e8e714d1c191a1e8342c Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 12:06:56 +0200 Subject: [PATCH 04/17] Omit isVisible from state --- packages/core/src/js/feedback/FeedbackWidget.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 839617beeb..e992f08d37 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,8 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', description: '', @@ -273,7 +272,6 @@ export class FeedbackWidget extends React.Component { FeedbackWidget._savedState = { - isVisible: false, name: '', email: '', description: '', From 59f2f570ae0e6bb6744bb59566cdc13e549add75 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 15:02:07 +0200 Subject: [PATCH 05/17] Save/clear form state on unmount --- .../core/src/js/feedback/FeedbackWidget.tsx | 27 ++++++++++++++----- .../test/feedback/FeedbackWidget.test.tsx | 21 +++++++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index e992f08d37..8ad7f85ded 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,6 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -103,7 +104,7 @@ export class FeedbackWidget extends React.Component { if (data != null) { - this.setState({ filename, attachment: data }, this._saveFormState); + this.setState({ filename, attachment: data }); } else { logger.error('Failed to read image data from uri:', imageUri); } @@ -153,11 +154,23 @@ export class FeedbackWidget extends React.Component { - this.setState({ filename, attachment: attachement }, this._saveFormState); + this.setState({ filename, attachment: attachement }); }); } } else { - this.setState({ filename: undefined, attachment: undefined }, this._saveFormState); + this.setState({ filename: undefined, attachment: undefined }); + } + } + + /** + * Save the state before unmounting the component. + */ + public componentWillUnmount(): void { + if (FeedbackWidget._didSubmitForm) { + this._clearFormState(); + FeedbackWidget._didSubmitForm = false; + } else { + this._saveFormState(); } } @@ -210,7 +223,7 @@ export class FeedbackWidget extends React.Component this.setState({ name: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ name: value })} /> )} @@ -226,7 +239,7 @@ export class FeedbackWidget extends React.Component this.setState({ email: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ email: value })} /> )} @@ -239,7 +252,7 @@ export class FeedbackWidget extends React.Component this.setState({ description: value }, this._saveFormState)} + onChangeText={(value) => this.setState({ description: value })} multiline /> {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( diff --git a/packages/core/test/feedback/FeedbackWidget.test.tsx b/packages/core/test/feedback/FeedbackWidget.test.tsx index c3fcc8fefe..0274651a31 100644 --- a/packages/core/test/feedback/FeedbackWidget.test.tsx +++ b/packages/core/test/feedback/FeedbackWidget.test.tsx @@ -355,14 +355,30 @@ describe('FeedbackWidget', () => { expect(mockOnFormClose).toHaveBeenCalled(); }); + it('onUnmount the input is saved and restored when the form reopens', async () => { + const { getByPlaceholderText, unmount } = render(); + + fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); + fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); + fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); + + unmount(); + const { queryByPlaceholderText } = render(); + + expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); + expect(queryByPlaceholderText(defaultProps.emailPlaceholder).props.value).toBe('john.doe@example.com'); + expect(queryByPlaceholderText(defaultProps.messagePlaceholder).props.value).toBe('This is a feedback message.'); + }); + it('onCancel the input is saved and restored when the form reopens', async () => { - const { getByPlaceholderText, getByText } = render(); + const { getByPlaceholderText, getByText, unmount } = render(); fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); fireEvent.press(getByText(defaultProps.cancelButtonLabel)); + unmount(); const { queryByPlaceholderText } = render(); expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('John Doe'); @@ -371,13 +387,14 @@ describe('FeedbackWidget', () => { }); it('onSubmit the saved input is cleared and not restored when the form reopens', async () => { - const { getByPlaceholderText, getByText } = render(); + const { getByPlaceholderText, getByText, unmount } = render(); fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe'); fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com'); fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.'); fireEvent.press(getByText(defaultProps.submitButtonLabel)); + unmount(); const { queryByPlaceholderText } = render(); expect(queryByPlaceholderText(defaultProps.namePlaceholder).props.value).toBe('Test User'); From 797611f898676086b4e9f3570244cf8783245055 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 15:02:42 +0200 Subject: [PATCH 06/17] Pass the missing attachment parameter in the onSubmitSuccess --- packages/core/src/js/feedback/FeedbackWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 8ad7f85ded..b1da634aef 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -101,7 +101,7 @@ export class FeedbackWidget extends React.Component Date: Mon, 17 Feb 2025 15:43:46 +0200 Subject: [PATCH 07/17] Use only the uri parameter for the onAddScreenshot callback --- packages/core/src/js/feedback/FeedbackWidget.tsx | 10 +++++----- packages/core/src/js/feedback/FeedbackWidget.types.ts | 2 +- packages/core/src/js/feedback/defaults.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index ad91d7890b..7cc28cdc9f 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -155,16 +155,16 @@ export class FeedbackWidget extends React.Component { - NATIVE.getDataFromUri(fileUri).then((data) => { + onAddScreenshot((uri: string) => { + NATIVE.getDataFromUri(uri).then((data) => { if (data != null) { - this.setState({ filename, attachment: data, attachmentUri: fileUri }); + this.setState({ filename: 'feedback_screenshot', attachment: data, attachmentUri: uri }); } else { - logger.error('Failed to read image data from uri:', fileUri); + logger.error('Failed to read image data from uri:', uri); } }) .catch((error) => { - logger.error('Failed to read image data from uri:', fileUri, 'error: ', error); + logger.error('Failed to read image data from uri:', uri, 'error: ', error); }); }); } diff --git a/packages/core/src/js/feedback/FeedbackWidget.types.ts b/packages/core/src/js/feedback/FeedbackWidget.types.ts index 2f369f9b1b..2c231c0594 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.types.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.types.ts @@ -171,7 +171,7 @@ export interface FeedbackCallbacks { /** * Callback when a screenshot is added */ - onAddScreenshot?: (attachFile: (filename: string, fileUri: string) => void) => void; + onAddScreenshot?: (screenshotAdded: (uri: string) => void) => void; /** * Callback when feedback is successfully submitted diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index a17e5d8541..1b7ba84ce9 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -33,7 +33,7 @@ export const defaultConfiguration: Partial = { ); } }, - onAddScreenshot: (_: (filename: string, fileUri: string) => void) => { + onAddScreenshot: (_: (uri: string) => void) => { if (__DEV__) { Alert.alert('Development note', 'onAddScreenshot callback is not implemented.'); } From 41cb1cab3fca8306d98fd36c967cc38ced8a8763 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 18:19:49 +0200 Subject: [PATCH 08/17] Handle attachments on the web --- .../core/src/js/feedback/FeedbackWidget.tsx | 30 +++++++++++++------ .../src/js/feedback/FeedbackWidget.types.ts | 3 ++ packages/core/src/js/feedback/utils.ts | 21 ++++++++++++- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 1136ef7e7b..6a96ffe524 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -17,12 +17,13 @@ import { View } from 'react-native'; +import { isWeb } from '../utils/environment'; import { NATIVE } from '../wrapper'; import { sentryLogo } from './branding'; import { defaultConfiguration } from './defaults'; import defaultStyles from './FeedbackWidget.styles'; import type { FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackWidgetProps, FeedbackWidgetState, FeedbackWidgetStyles, ImagePickerConfiguration } from './FeedbackWidget.types'; -import { isValidEmail } from './utils'; +import { base64ToUint8Array, isValidEmail } from './utils'; /** * @beta @@ -123,10 +124,10 @@ export class FeedbackWidget extends React.Component imagePickerConfiguration.imagePicker.launchImageLibraryAsync({ mediaTypes: ['images'] }) + ? () => imagePickerConfiguration.imagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], base64: isWeb() }) // react-native-image-picker library is available : imagePickerConfiguration.imagePicker.launchImageLibrary - ? () => imagePickerConfiguration.imagePicker.launchImageLibrary({ mediaType: 'photo' }) + ? () => imagePickerConfiguration.imagePicker.launchImageLibrary({ mediaType: 'photo', includeBase64: isWeb() }) : null; if (!launchImageLibrary) { logger.warn('No compatible image picker library found. Please provide a valid image picker library.'); @@ -141,18 +142,29 @@ export class FeedbackWidget extends React.Component 0) { - const filename = result.assets[0].fileName; - const imageUri = result.assets[0].uri; - NATIVE.getDataFromUri(imageUri).then((data) => { + if (isWeb()) { + const filename = result.assets[0].fileName; + const imageUri = result.assets[0].uri; + const base64 = result.assets[0].base64; + const data = base64ToUint8Array(base64); if (data != null) { this.setState({ filename, attachment: data, attachmentUri: imageUri }); } else { - logger.error('Failed to read image data from uri:', imageUri); + logger.error('Failed to read image data on the web'); } - }) - .catch((error) => { + } else { + const filename = result.assets[0].fileName; + const imageUri = result.assets[0].uri; + NATIVE.getDataFromUri(imageUri).then((data) => { + if (data != null) { + this.setState({ filename, attachment: data, attachmentUri: imageUri }); + } else { + logger.error('Failed to read image data from uri:', imageUri); + } + }).catch((error) => { logger.error('Failed to read image data from uri:', imageUri, 'error: ', error); }); + } } } else { // Defaulting to the onAddScreenshot callback diff --git a/packages/core/src/js/feedback/FeedbackWidget.types.ts b/packages/core/src/js/feedback/FeedbackWidget.types.ts index 2c231c0594..f3349c9bcc 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.types.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.types.ts @@ -207,14 +207,17 @@ interface ImagePickerResponse { interface ImagePickerAsset { fileName?: string; uri?: string; + base64?: string; } interface ExpoImageLibraryOptions { mediaTypes?: 'images'[]; + base64?: boolean; } interface ReactNativeImageLibraryOptions { mediaType: 'photo'; + includeBase64?: boolean; } export interface ImagePicker { diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts index b27fb3ea8d..80291211b9 100644 --- a/packages/core/src/js/feedback/utils.ts +++ b/packages/core/src/js/feedback/utils.ts @@ -1,6 +1,11 @@ -import { isFabricEnabled } from '../utils/environment'; +import { isFabricEnabled, isWeb } from '../utils/environment'; import { ReactNativeLibraries } from './../utils/rnlibraries'; +declare global { + // Declaring atob function to be used in web environment + function atob(encodedString: string): string; +} + /** * Modal is not supported in React Native < 0.71 with Fabric renderer. * ref: https://github.com/facebook/react-native/issues/33652 @@ -14,3 +19,17 @@ export const isValidEmail = (email: string): boolean => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; return emailRegex.test(email); }; + +/** + * Converts base64 string to Uint8Array on the web + * @param base64 base64 string + * @returns Uint8Array data + */ +export const base64ToUint8Array = (base64: string): Uint8Array => { + if (typeof atob !== 'function' || !isWeb()) { + throw new Error('atob is not available in this environment.'); + } + + const binaryString = atob(base64); + return new Uint8Array([...binaryString].map(char => char.charCodeAt(0))); +}; From f5c306f3009bcad75e9c04555c03236ad1835920 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 19:15:06 +0200 Subject: [PATCH 09/17] Use window for showing alerts on the web --- packages/core/src/js/feedback/FeedbackWidget.tsx | 13 ++++++------- packages/core/src/js/feedback/defaults.ts | 9 ++++----- packages/core/src/js/feedback/utils.ts | 14 ++++++++++++++ .../core/test/feedback/FeedbackWidget.test.tsx | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 6a96ffe524..9a505393eb 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -3,7 +3,6 @@ import { captureFeedback, getCurrentScope, lastEventId, logger } from '@sentry/c import * as React from 'react'; import type { KeyboardTypeOptions } from 'react-native'; import { - Alert, Image, Keyboard, KeyboardAvoidingView, @@ -23,7 +22,7 @@ import { sentryLogo } from './branding'; import { defaultConfiguration } from './defaults'; import defaultStyles from './FeedbackWidget.styles'; import type { FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackWidgetProps, FeedbackWidgetState, FeedbackWidgetStyles, ImagePickerConfiguration } from './FeedbackWidget.types'; -import { base64ToUint8Array, isValidEmail } from './utils'; +import { base64ToUint8Array, feedbackAlertDialog, isValidEmail } from './utils'; /** * @beta @@ -75,12 +74,12 @@ export class FeedbackWidget extends React.Component 0) && !isValidEmail(trimmedEmail)) { - Alert.alert(text.errorTitle, text.emailError); + feedbackAlertDialog(text.errorTitle, text.emailError); return; } @@ -107,13 +106,13 @@ export class FeedbackWidget extends React.Component = { }, onFormClose: () => { if (__DEV__) { - Alert.alert( + feedbackAlertDialog( 'Development note', 'onFormClose callback is not implemented. By default the form is just unmounted.', ); @@ -35,7 +34,7 @@ export const defaultConfiguration: Partial = { }, onAddScreenshot: (_: (uri: string) => void) => { if (__DEV__) { - Alert.alert('Development note', 'onAddScreenshot callback is not implemented.'); + feedbackAlertDialog('Development note', 'onAddScreenshot callback is not implemented.'); } }, onSubmitSuccess: () => { @@ -46,7 +45,7 @@ export const defaultConfiguration: Partial = { }, onFormSubmitted: () => { if (__DEV__) { - Alert.alert( + feedbackAlertDialog( 'Development note', 'onFormSubmitted callback is not implemented. By default the form is just unmounted.', ); diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts index 80291211b9..8b05731980 100644 --- a/packages/core/src/js/feedback/utils.ts +++ b/packages/core/src/js/feedback/utils.ts @@ -1,3 +1,5 @@ +import { Alert } from 'react-native'; + import { isFabricEnabled, isWeb } from '../utils/environment'; import { ReactNativeLibraries } from './../utils/rnlibraries'; @@ -33,3 +35,15 @@ export const base64ToUint8Array = (base64: string): Uint8Array => { const binaryString = atob(base64); return new Uint8Array([...binaryString].map(char => char.charCodeAt(0))); }; + +export const feedbackAlertDialog = (title: string, message: string): void => { + /* eslint-disable @typescript-eslint/ban-ts-comment, no-restricted-globals, no-alert, @typescript-eslint/no-unsafe-member-access */ + // @ts-ignore + if (isWeb() && typeof window !== 'undefined') { + // @ts-ignore + window.alert(`${title}\n${message}`); + /* eslint-enable @typescript-eslint/ban-ts-comment, no-restricted-globals, no-alert, @typescript-eslint/no-unsafe-member-access */ + } else { + Alert.alert(title, message); + } +}; diff --git a/packages/core/test/feedback/FeedbackWidget.test.tsx b/packages/core/test/feedback/FeedbackWidget.test.tsx index 0274651a31..fb5a394fa3 100644 --- a/packages/core/test/feedback/FeedbackWidget.test.tsx +++ b/packages/core/test/feedback/FeedbackWidget.test.tsx @@ -235,7 +235,7 @@ describe('FeedbackWidget', () => { fireEvent.press(getByText(defaultProps.submitButtonLabel)); await waitFor(() => { - expect(Alert.alert).toHaveBeenCalledWith(defaultProps.successMessageText); + expect(Alert.alert).toHaveBeenCalledWith(defaultProps.successMessageText, ''); }); }); From 42ab16c16b74bd7f236b3d252beb1b1c4c4b4e10 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 17 Feb 2025 19:18:02 +0200 Subject: [PATCH 10/17] Disable keyboard handling on the web --- packages/core/src/js/feedback/FeedbackWidget.tsx | 5 +++-- packages/core/src/js/feedback/FeedbackWidgetManager.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 9a505393eb..79c77a3664 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -16,7 +16,7 @@ import { View } from 'react-native'; -import { isWeb } from '../utils/environment'; +import { isWeb, notWeb } from '../utils/environment'; import { NATIVE } from '../wrapper'; import { sentryLogo } from './branding'; import { defaultConfiguration } from './defaults'; @@ -225,9 +225,10 @@ export class FeedbackWidget extends React.Component - + {text.formTitle} diff --git a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx index 11a7c3f8cc..48439a07f1 100644 --- a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx +++ b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx @@ -2,6 +2,7 @@ import { logger } from '@sentry/core'; import * as React from 'react'; import { Animated, Dimensions, Easing, KeyboardAvoidingView, Modal, PanResponder, Platform } from 'react-native'; +import { notWeb } from '../utils/environment'; import { FeedbackWidget } from './FeedbackWidget'; import { modalBackground, modalSheetContainer, modalWrapper } from './FeedbackWidget.styles'; import type { FeedbackWidgetStyles } from './FeedbackWidget.types'; @@ -61,10 +62,10 @@ class FeedbackWidgetProvider extends React.Component { // On Android allow pulling down only from the top to avoid breaking native gestures - return Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT; + return notWeb() && (Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT); }, onMoveShouldSetPanResponder: (evt, _gestureState) => { - return Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT; + return notWeb() && (Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT); }, onPanResponderMove: (_, gestureState) => { if (gestureState.dy > 0) { @@ -147,6 +148,7 @@ class FeedbackWidgetProvider extends React.Component Date: Tue, 18 Feb 2025 11:38:18 +0200 Subject: [PATCH 11/17] Use instance variable for _didSubmitForm --- packages/core/src/js/feedback/FeedbackWidget.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 92d2f6d3b8..c5415f4ddc 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,7 +33,7 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -106,7 +106,7 @@ export class FeedbackWidget extends React.Component Date: Tue, 18 Feb 2025 11:41:49 +0200 Subject: [PATCH 12/17] Fixed callback function parameter name for clarity Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> --- packages/core/src/js/feedback/FeedbackWidget.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.types.ts b/packages/core/src/js/feedback/FeedbackWidget.types.ts index 2c231c0594..e821f886b8 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.types.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.types.ts @@ -171,7 +171,7 @@ export interface FeedbackCallbacks { /** * Callback when a screenshot is added */ - onAddScreenshot?: (screenshotAdded: (uri: string) => void) => void; + onAddScreenshot?: (addScreenshot: (uri: string) => void) => void; /** * Callback when feedback is successfully submitted From 6e2d6eca1561cb052f088cf1736e3b2f0a45f86d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 18 Feb 2025 11:45:18 +0200 Subject: [PATCH 13/17] Fixes lint issue --- packages/core/src/js/feedback/FeedbackWidget.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index c5415f4ddc..f0026751d3 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -33,7 +33,6 @@ export class FeedbackWidget extends React.Component = { name: '', email: '', @@ -42,6 +41,8 @@ export class FeedbackWidget extends React.Component Date: Tue, 18 Feb 2025 12:08:24 +0200 Subject: [PATCH 14/17] Use RN_GLOBAL_OBJ for web alert --- packages/core/src/js/feedback/utils.ts | 9 +++------ packages/core/src/js/utils/worldwide.ts | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts index 8b05731980..9c2826981d 100644 --- a/packages/core/src/js/feedback/utils.ts +++ b/packages/core/src/js/feedback/utils.ts @@ -1,6 +1,7 @@ import { Alert } from 'react-native'; import { isFabricEnabled, isWeb } from '../utils/environment'; +import { RN_GLOBAL_OBJ } from '../utils/worldwide'; import { ReactNativeLibraries } from './../utils/rnlibraries'; declare global { @@ -37,12 +38,8 @@ export const base64ToUint8Array = (base64: string): Uint8Array => { }; export const feedbackAlertDialog = (title: string, message: string): void => { - /* eslint-disable @typescript-eslint/ban-ts-comment, no-restricted-globals, no-alert, @typescript-eslint/no-unsafe-member-access */ - // @ts-ignore - if (isWeb() && typeof window !== 'undefined') { - // @ts-ignore - window.alert(`${title}\n${message}`); - /* eslint-enable @typescript-eslint/ban-ts-comment, no-restricted-globals, no-alert, @typescript-eslint/no-unsafe-member-access */ + if (isWeb() && typeof RN_GLOBAL_OBJ.alert !== 'undefined') { + RN_GLOBAL_OBJ.alert(`${title}\n${message}`); } else { Alert.alert(title, message); } diff --git a/packages/core/src/js/utils/worldwide.ts b/packages/core/src/js/utils/worldwide.ts index c1a4ae5dbb..03327bac36 100644 --- a/packages/core/src/js/utils/worldwide.ts +++ b/packages/core/src/js/utils/worldwide.ts @@ -25,6 +25,7 @@ export interface ReactNativeInternalGlobal extends InternalGlobal { __BUNDLE_START_TIME__?: number; nativePerformanceNow?: () => number; TextEncoder?: TextEncoder; + alert?: (message: string) => void; } type TextEncoder = { From 57134179173ffdacc659a8d99620bdaf2410b36a Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 19:51:29 +0100 Subject: [PATCH 15/17] misc(feedback): Improve Feedback Sheet interactions --- .../src/js/feedback/FeedbackWidget.styles.ts | 24 +-- .../core/src/js/feedback/FeedbackWidget.tsx | 164 ++++++++---------- .../src/js/feedback/FeedbackWidgetManager.tsx | 50 +++--- 3 files changed, 117 insertions(+), 121 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.styles.ts b/packages/core/src/js/feedback/FeedbackWidget.styles.ts index f7adc5f214..1c28f4fa7f 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.styles.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.styles.ts @@ -3,15 +3,15 @@ import type { ViewStyle } from 'react-native'; import type { FeedbackWidgetStyles } from './FeedbackWidget.types'; const PURPLE = 'rgba(88, 74, 192, 1)'; -const FORGROUND_COLOR = '#2b2233'; -const BACKROUND_COLOR = '#ffffff'; +const FOREGROUND_COLOR = '#2b2233'; +const BACKGROUND_COLOR = '#ffffff'; const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)'; const defaultStyles: FeedbackWidgetStyles = { container: { flex: 1, padding: 20, - backgroundColor: BACKROUND_COLOR, + backgroundColor: BACKGROUND_COLOR, }, title: { fontSize: 24, @@ -19,12 +19,12 @@ const defaultStyles: FeedbackWidgetStyles = { marginBottom: 20, textAlign: 'left', flex: 1, - color: FORGROUND_COLOR, + color: FOREGROUND_COLOR, }, label: { marginBottom: 4, fontSize: 16, - color: FORGROUND_COLOR, + color: FOREGROUND_COLOR, }, input: { height: 50, @@ -34,12 +34,12 @@ const defaultStyles: FeedbackWidgetStyles = { paddingHorizontal: 10, marginBottom: 15, fontSize: 16, - color: FORGROUND_COLOR, + color: FOREGROUND_COLOR, }, textArea: { height: 100, textAlignVertical: 'top', - color: FORGROUND_COLOR, + color: FOREGROUND_COLOR, }, screenshotButton: { backgroundColor: '#eee', @@ -72,7 +72,7 @@ const defaultStyles: FeedbackWidgetStyles = { marginBottom: 10, }, submitText: { - color: BACKROUND_COLOR, + color: BACKGROUND_COLOR, fontSize: 18, }, cancelButton: { @@ -80,7 +80,7 @@ const defaultStyles: FeedbackWidgetStyles = { alignItems: 'center', }, cancelText: { - color: FORGROUND_COLOR, + color: FOREGROUND_COLOR, fontSize: 16, }, titleContainer: { @@ -112,12 +112,16 @@ export const modalSheetContainer: ViewStyle = { borderTopRightRadius: 16, overflow: 'hidden', alignSelf: 'stretch', - height: '92%', shadowColor: '#000', shadowOffset: { width: 0, height: -3 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 5, + flex: 1, +}; + +export const topSpacer: ViewStyle = { + height: 64, // magic number }; export default defaultStyles; diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index bb6931e759..2d81a9e7e6 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -5,10 +5,6 @@ import type { KeyboardTypeOptions } from 'react-native'; import { Image, Keyboard, - KeyboardAvoidingView, - Platform, - SafeAreaView, - ScrollView, Text, TextInput, TouchableOpacity, @@ -222,97 +218,87 @@ export class FeedbackWidget extends React.Component - - - - - - {text.formTitle} - {config.showBranding && ( - - )} - + + + + {text.formTitle} + {config.showBranding && ( + + )} + - {config.showName && ( - <> - - {text.nameLabel} - {config.isNameRequired && ` ${text.isRequiredLabel}`} - - this.setState({ name: value })} - /> - - )} + {config.showName && ( + <> + + {text.nameLabel} + {config.isNameRequired && ` ${text.isRequiredLabel}`} + + this.setState({ name: value })} + /> + + )} - {config.showEmail && ( - <> - - {text.emailLabel} - {config.isEmailRequired && ` ${text.isRequiredLabel}`} - - this.setState({ email: value })} - /> - - )} + {config.showEmail && ( + <> + + {text.emailLabel} + {config.isEmailRequired && ` ${text.isRequiredLabel}`} + + this.setState({ email: value })} + /> + + )} - - {text.messageLabel} - {` ${text.isRequiredLabel}`} - - this.setState({ description: value })} - multiline - /> - {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( - - {this.state.attachmentUri && ( - - )} - - - {!this.state.filename && !this.state.attachment - ? text.addScreenshotButtonLabel - : text.removeScreenshotButtonLabel} - - - + + {text.messageLabel} + {` ${text.isRequiredLabel}`} + + this.setState({ description: value })} + multiline + /> + {(config.enableScreenshot || imagePickerConfiguration.imagePicker) && ( + + {this.state.attachmentUri && ( + )} - - {text.submitButtonLabel} - - - - {text.cancelButtonLabel} + + + {!this.state.filename && !this.state.attachment + ? text.addScreenshotButtonLabel + : text.removeScreenshotButtonLabel} + - - - - + )} + + {text.submitButtonLabel} + + + + {text.cancelButtonLabel} + + + ); } diff --git a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx index 48439a07f1..52d20ee19b 100644 --- a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx +++ b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx @@ -1,16 +1,16 @@ import { logger } from '@sentry/core'; import * as React from 'react'; -import { Animated, Dimensions, Easing, KeyboardAvoidingView, Modal, PanResponder, Platform } from 'react-native'; +import type { NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; +import { Animated, Dimensions, Easing, Modal, PanResponder, Platform, ScrollView, View } from 'react-native'; import { notWeb } from '../utils/environment'; import { FeedbackWidget } from './FeedbackWidget'; -import { modalBackground, modalSheetContainer, modalWrapper } from './FeedbackWidget.styles'; +import { modalBackground, modalSheetContainer, modalWrapper, topSpacer } from './FeedbackWidget.styles'; import type { FeedbackWidgetStyles } from './FeedbackWidget.types'; import { getFeedbackOptions } from './integration'; import { isModalSupported } from './utils'; const PULL_DOWN_CLOSE_THREESHOLD = 200; -const PULL_DOWN_ANDROID_ACTIVATION_HEIGHT = 150; const SLIDE_ANIMATION_DURATION = 200; const BACKGROUND_ANIMATION_DURATION = 200; @@ -50,6 +50,7 @@ interface FeedbackWidgetProviderState { isVisible: boolean; backgroundOpacity: Animated.Value; panY: Animated.Value; + isScrollAtTop: boolean; } class FeedbackWidgetProvider extends React.Component { @@ -57,15 +58,16 @@ class FeedbackWidgetProvider extends React.Component { + onStartShouldSetPanResponder: (_, gestureState) => { // On Android allow pulling down only from the top to avoid breaking native gestures - return notWeb() && (Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT); + return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0; }, - onMoveShouldSetPanResponder: (evt, _gestureState) => { - return notWeb() && (Platform.OS !== 'android' || evt.nativeEvent.pageY < PULL_DOWN_ANDROID_ACTIVATION_HEIGHT); + onMoveShouldSetPanResponder: (_, gestureState) => { + return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0; }, onPanResponderMove: (_, gestureState) => { if (gestureState.dy > 0) { @@ -142,31 +144,35 @@ class FeedbackWidgetProvider extends React.Component {this.props.children} - {isVisible && ( - + {isVisible && + - - + + + - - + /> + + - )} + } ); } + private _handleScroll = (event: NativeSyntheticEvent): void => { + this.setState({ isScrollAtTop: event.nativeEvent.contentOffset.y <= 0 }); + }; + private _setVisibilityFunction = (visible: boolean): void => { const updateState = (): void => { this.setState({ isVisible: visible }); From a74f36f8fb5cde980239c56c4990f9f6052a7fa9 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 20:21:37 +0100 Subject: [PATCH 16/17] small fixes --- .../core/src/js/feedback/FeedbackWidget.styles.ts | 5 ----- .../core/src/js/feedback/FeedbackWidgetManager.tsx | 13 +++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/core/src/js/feedback/FeedbackWidget.styles.ts b/packages/core/src/js/feedback/FeedbackWidget.styles.ts index 1c28f4fa7f..2144b51dfa 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.styles.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.styles.ts @@ -101,11 +101,6 @@ export const modalWrapper: ViewStyle = { bottom: 0, }; -export const modalBackground: ViewStyle = { - flex: 1, - justifyContent: 'flex-end', -}; - export const modalSheetContainer: ViewStyle = { backgroundColor: '#ffffff', borderTopLeftRadius: 16, diff --git a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx index 52d20ee19b..d10c356794 100644 --- a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx +++ b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx @@ -5,12 +5,12 @@ import { Animated, Dimensions, Easing, Modal, PanResponder, Platform, ScrollView import { notWeb } from '../utils/environment'; import { FeedbackWidget } from './FeedbackWidget'; -import { modalBackground, modalSheetContainer, modalWrapper, topSpacer } from './FeedbackWidget.styles'; +import { modalSheetContainer, modalWrapper, topSpacer } from './FeedbackWidget.styles'; import type { FeedbackWidgetStyles } from './FeedbackWidget.types'; import { getFeedbackOptions } from './integration'; import { isModalSupported } from './utils'; -const PULL_DOWN_CLOSE_THREESHOLD = 200; +const PULL_DOWN_CLOSE_THRESHOLD = 200; const SLIDE_ANIMATION_DURATION = 200; const BACKGROUND_ANIMATION_DURATION = 200; @@ -63,7 +63,6 @@ class FeedbackWidgetProvider extends React.Component { - // On Android allow pulling down only from the top to avoid breaking native gestures return notWeb() && this.state.isScrollAtTop && gestureState.dy > 0; }, onMoveShouldSetPanResponder: (_, gestureState) => { @@ -75,7 +74,8 @@ class FeedbackWidgetProvider extends React.Component { - if (gestureState.dy > PULL_DOWN_CLOSE_THREESHOLD) { // Close on swipe below a certain threshold + if (gestureState.dy > PULL_DOWN_CLOSE_THRESHOLD) { + // Close on swipe below a certain threshold Animated.timing(this.state.panY, { toValue: Dimensions.get('screen').height, duration: SLIDE_ANIMATION_DURATION, @@ -83,7 +83,8 @@ class FeedbackWidgetProvider extends React.Component { this._handleClose(); }); - } else { // Animate it back to the original position + } else { + // Animate it back to the original position Animated.spring(this.state.panY, { toValue: 0, useNativeDriver: true, @@ -145,7 +146,7 @@ class FeedbackWidgetProvider extends React.Component {this.props.children} {isVisible && - + Date: Wed, 19 Feb 2025 20:44:46 +0100 Subject: [PATCH 17/17] update snapshots --- .../FeedbackWidget.test.tsx.snap | 3404 ++++++++--------- 1 file changed, 1580 insertions(+), 1824 deletions(-) diff --git a/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap b/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap index 902c5a0d2f..937410c22d 100644 --- a/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap +++ b/packages/core/test/feedback/__snapshots__/FeedbackWidget.test.tsx.snap @@ -1,1924 +1,1680 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FeedbackWidget matches the snapshot with custom styles 1`] = ` - + + Report a Bug + + + + + Name + + + + Email + + + + Description + (required) + + + - - - - - - Report a Bug - - - - - Name - - - - Email - - - - Description - (required) - - - - - Send Bug Report - - - - - Cancel - - - - - + Send Bug Report + + + + + Cancel + - + `; exports[`FeedbackWidget matches the snapshot with custom styles and screenshot button 1`] = ` - + + Report a Bug + + + + + Name + + + + Email + + + + Description + (required) + + + - - - - - - Report a Bug - - - - - Name - - - - Email - - - - Description - (required) - - - - - - Add a screenshot - - - - - - Send Bug Report - - - - - Cancel - - - - - + } + > + Add a screenshot + + + + + + Send Bug Report + + + + + Cancel + - + `; exports[`FeedbackWidget matches the snapshot with custom texts 1`] = ` - + + Feedback Form + + + + + Name Label + + + + Email Label + + + + Message Label + (is required label) + + + - - - - - - Feedback Form - - - - - Name Label - - - - Email Label - - - - Message Label - (is required label) - - - - - Submit Button Label - - - - - Cancel Button Label - - - - - + Submit Button Label + + + + + Cancel Button Label + - + `; exports[`FeedbackWidget matches the snapshot with custom texts and screenshot button 1`] = ` - + + Feedback Form + + + + + Name Label + + + + Email Label + + + + Message Label + (is required label) + + + - - - - - - Feedback Form - - - - - Name Label - - - - Email Label - - - - Message Label - (is required label) - - - - - - Add Screenshot - - - - - - Submit Button Label - - - - - Cancel Button Label - - - - - + } + > + Add Screenshot + + + + + + Submit Button Label + + + + + Cancel Button Label + - + `; exports[`FeedbackWidget matches the snapshot with default configuration 1`] = ` - + + Report a Bug + + + + + Name + + + + Email + + + + Description + (required) + + + - - - - - - Report a Bug - - - - - Name - - - - Email - - - - Description - (required) - - - - - Send Bug Report - - - - - Cancel - - - - - + Send Bug Report + + + + + Cancel + - + `; exports[`FeedbackWidget matches the snapshot with default configuration and screenshot button 1`] = ` - + + Report a Bug + + + + + Name + + + + Email + + + + Description + (required) + + + - - - - - - Report a Bug - - - - - Name - - - - Email - - - - Description - (required) - - - - - - Add a screenshot - - - - - - Send Bug Report - - - - - Cancel - - - - - + } + > + Add a screenshot + + + + + + Send Bug Report + + + + + Cancel + - + `;