Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/src/tests/single-feature-tests/form-sheet/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ScenarioGroup } from '@apps/tests/shared/helpers';
import TestFormSheetBase from './test-form-sheet-base-ios';
import TestFormSheetDismissEvents from './test-form-sheet-dismiss-events-ios';
import TestFormSheetExpandScrollView from './test-form-sheet-expand-scroll-view-ios';
import TestFormSheetFitToContents from './test-form-sheet-fit-to-contents-ios';
import TestFormSheetGrabberVisible from './test-form-sheet-grabber-visible-ios';
Expand All @@ -13,6 +14,7 @@ import TestFormSheetPreventNativeDismiss from './test-form-sheet-prevent-native-

const scenarios = {
TestFormSheetBase,
TestFormSheetDismissEvents,
TestFormSheetExpandScrollView,
TestFormSheetFitToContents,
TestFormSheetGrabberVisible,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useState } from 'react';
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native';
import { FormSheet } from 'react-native-screens/experimental';
import { scenarioDescription } from './scenario-description';
import { createScenario } from '@apps/tests/shared/helpers';
import { Colors } from '@apps/shared/styling';

export function App() {
const [isOpen, setIsOpen] = useState(false);
const [logs, setLogs] = useState<string[]>([]);

const addLog = (eventName: string) => {
const timestamp = new Date().toISOString().substring(11, 23);
setLogs(prev => [...prev, `[${timestamp}] ${eventName}`]);
};

return (
<View style={styles.container}>
<Text style={styles.title}>FormSheet Dismiss Test</Text>

<View style={styles.row}>
<Button
title="Open FormSheet"
color={Colors.primary}
onPress={() => setIsOpen(true)}
/>
<Button
title="Clear Logs"
color={Colors.primary}
onPress={() => setLogs([])}
/>
</View>

<View style={styles.logsContainer}>
<Text style={styles.logsHeader}>Event Logs:</Text>
<ScrollView style={styles.scrollView}>
{logs.map((log, index) => (
<Text key={index} style={styles.logText}>
{log}
</Text>
))}
{logs.length === 0 && (
<Text style={styles.emptyLogText}>No events recorded yet.</Text>
)}
</ScrollView>
</View>

<FormSheet
isOpen={isOpen}
onNativeDismiss={() => {
setIsOpen(false);
addLog('onNativeDismiss');
}}
onDismiss={() => addLog('onDismiss')}
detents={[0.6, 1.0]}>
<View style={styles.sheetContent}>
<Text style={styles.sheetTitle}>FormSheet Content</Text>
<View style={styles.spacing} />
<Button
title="Dismiss from JS"
color={Colors.primary}
onPress={() => setIsOpen(false)}
/>
</View>
</FormSheet>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 60,
alignItems: 'center',
backgroundColor: Colors.offBackground,
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20,
color: Colors.text,
},
row: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
gap: 16,
},
logsContainer: {
flex: 1,
width: '100%',
paddingHorizontal: 24,
paddingBottom: 24,
},
logsHeader: {
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
color: Colors.text,
},
scrollView: {
flex: 1,
backgroundColor: Colors.background,
borderRadius: 8,
padding: 12,
borderWidth: 1,
borderColor: '#ccc',
},
logText: {
fontSize: 14,
color: Colors.text,
marginBottom: 4,
fontFamily: 'Courier',
},
emptyLogText: {
fontSize: 14,
color: 'gray',
fontStyle: 'italic',
},
sheetContent: {
flex: 1,
backgroundColor: Colors.background,
padding: 24,
justifyContent: 'center',
alignItems: 'center',
},
sheetTitle: {
fontSize: 22,
fontWeight: '600',
marginBottom: 12,
color: Colors.text,
},
spacing: {
height: 32,
},
});

export default createScenario(App, scenarioDescription);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ScenarioDescription } from '@apps/tests/shared/helpers';

export const scenarioDescription: ScenarioDescription = {
name: 'Dismiss Events',
key: 'test-form-sheet-dismiss-events-ios',
details:
'Allows to test the dismiss events (onDismiss, onNativeDismiss) of the FormSheet component.',
platforms: ['ios'],
e2eCoverage: 'tbd',
smokeTest: false,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Test Scenario: Dismiss Events

## Details

**Description:** Verify that the `FormSheet` component correctly emits dismiss events. The `onDismiss` event should be fired every time the sheet is dismissed programmatically. The `onNativeDismiss` event should be fired when the user dismisses the sheet via a native gesture (e.g., swiping down or tapping the backdrop).

**OS test creation version:** iOS: 18.6 and 26.4

## E2E test

TBD: Planned, but will be implemented separately.

## Prerequisites

- iOS device or simulator: iPhone

## Steps

### Baseline

1. Launch the app and navigate to the **Dismiss Events** screen.

- [ ] An example with "Open FormSheet" and "Clear Logs" buttons is shown, along with an empty event logs area.

---

### Native Dismissal (Swiping down)

2. Tap the "Open FormSheet" button.
3. Wait for the sheet presentation animation to finish.
4. Dismiss the FormSheet by swiping it down to the bottom of the screen.

- [ ] The FormSheet dismisses smoothly and returns the user to the underlying main screen.
- [ ] The logs list updates to show new `onNativeDismiss` event added.

---

### Programmatic Dismissal (JS)

5. Tap "Clear Logs" to reset the list.
6. Tap the "Open FormSheet" button.
7. Wait for the sheet presentation animation to finish.
8. Tap the "Dismiss from JS" button inside the FormSheet.

- [ ] The FormSheet dismisses smoothly and returns the user to the underlying main screen.
- [ ] The logs list updates to show new `onDismiss` event added.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN
@class RNSFormSheetContentController;

@protocol RNSFormSheetContentControllerDelegate <NSObject>
- (void)sheetControllerDidDismiss:(RNSFormSheetContentController *)controller;
- (void)sheetControllerDidNativeDismiss:(RNSFormSheetContentController *)controller;
- (void)sheetControllerViewDidLayoutSubviews:(RNSFormSheetContentController *)controller;
#if !TARGET_OS_TV
Expand Down
5 changes: 5 additions & 0 deletions ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ - (void)contentWrapper:(RNSFormSheetContentWrapperComponentView *)wrapper

#pragma mark - RNSFormSheetContentControllerDelegate

- (void)sheetControllerDidDismiss:(RNSFormSheetContentController *)controller
{
[_reactEventEmitter emitOnDismiss];
}

- (void)sheetControllerDidNativeDismiss:(RNSFormSheetContentController *)controller
{
_isOpen = NO;
Expand Down
1 change: 1 addition & 0 deletions ios/gamma/modals/form-sheet/RNSFormSheetHostEventEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface RNSFormSheetHostEventEmitter : NSObject

- (BOOL)emitOnDismiss;
- (BOOL)emitOnNativeDismiss;
- (BOOL)emitOnNativeDismissPrevented;
#if !TARGET_OS_TV
Expand Down
11 changes: 11 additions & 0 deletions ios/gamma/modals/form-sheet/RNSFormSheetHostEventEmitter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ @implementation RNSFormSheetHostEventEmitter {
std::shared_ptr<const react::RNSFormSheetHostEventEmitter> _reactEventEmitter;
}

- (BOOL)emitOnDismiss
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onDismiss({});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnDismiss event emission due to nullish emitter");
return NO;
}
}

- (BOOL)emitOnNativeDismiss
{
if (_reactEventEmitter != nullptr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ - (void)dismissIfNeededWithProvider:(id<RNSFormSheetPresentationProvider>)provid

strongSelf->_state = RNSFormSheetPresentationStateDismissed;
[strongSelf updatePresentationIfNeededWithProvider:provider controller:controller];

[controller.delegate sheetControllerDidDismiss:controller];
}];
}

Expand Down
9 changes: 9 additions & 0 deletions src/components/gamma/modals/form-sheet/FormSheet.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ export interface FormSheetProps {
*/
onDidDisappear?: FormSheetEventHandler<EmptyEventPayload> | undefined;

/**
* @summary Called when the sheet is dismissed programmatically.
*
* This event is fired when the sheet was dismissed via the `isOpen` prop changing to `false`.
*
* @platform ios
*/
onDismiss?: FormSheetEventHandler<EmptyEventPayload> | undefined;

/**
* @summary Called when the sheet is dismissed natively.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface NativeProps extends ViewProps {
onDidAppear?: CT.DirectEventHandler<GenericEmptyEvent> | undefined;
onWillDisappear?: CT.DirectEventHandler<GenericEmptyEvent> | undefined;
onDidDisappear?: CT.DirectEventHandler<GenericEmptyEvent> | undefined;
onDismiss?: CT.DirectEventHandler<GenericEmptyEvent> | undefined;
onNativeDismiss?: CT.DirectEventHandler<GenericEmptyEvent> | undefined;
onNativeDismissPrevented?:
| CT.DirectEventHandler<GenericEmptyEvent>
Expand Down
Loading