-
-
Notifications
You must be signed in to change notification settings - Fork 654
feat(iOS, FormSheet v5): Add support for backgroundComponent #4100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d8c670e
5d0a71d
1acf99e
74903ba
10551df
3669b98
c2fe0c0
e4a34e4
6110362
2c73bb9
8716e74
009f820
8d36db4
914dcb2
6121c7d
7fbdaf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| import React, { useState } from 'react'; | ||
| import { | ||
| Button, | ||
| StyleSheet, | ||
| Text, | ||
| View, | ||
| Image, | ||
| Dimensions, | ||
| TouchableOpacity, | ||
| } 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'; | ||
|
|
||
| const SCREEN_WIDTH = Dimensions.get('window').width; | ||
|
|
||
| type BackgroundType = 'none' | 'solid' | 'composed' | 'image'; | ||
|
|
||
| export function App() { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const [bgType, setBgType] = useState<BackgroundType>('image'); | ||
|
|
||
| const handleDismiss = () => { | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| const SolidBackground = ( | ||
| <View | ||
| style={[ | ||
| StyleSheet.absoluteFill, | ||
| { backgroundColor: Colors.PurpleLight100 }, | ||
| ]} | ||
| /> | ||
| ); | ||
|
|
||
| const ComposedBackground = ( | ||
| <View | ||
| style={[ | ||
| StyleSheet.absoluteFill, | ||
| { backgroundColor: Colors.NavyLight80 }, | ||
| ]}> | ||
| <View style={styles.composedTopBar} /> | ||
| <View style={styles.composedBottomBar} /> | ||
| </View> | ||
| ); | ||
|
|
||
| const ImageBackground = ( | ||
| <Image | ||
| source={{ | ||
| uri: 'https://fastly.picsum.photos/id/541/900/600.jpg?hmac=W6n9QTOEWat4-wHywp8az4ZqvcDzUMWWR1XDq3kS9sI', | ||
| height: 600, | ||
| width: SCREEN_WIDTH, | ||
| }} | ||
| style={StyleSheet.absoluteFill} | ||
| /> | ||
| ); | ||
|
|
||
| const renderBackground = () => { | ||
| switch (bgType) { | ||
| case 'solid': | ||
| return SolidBackground; | ||
| case 'composed': | ||
| return ComposedBackground; | ||
| case 'image': | ||
| return ImageBackground; | ||
| case 'none': | ||
| default: | ||
| return undefined; | ||
| } | ||
| }; | ||
|
|
||
| const isDarkBg = bgType !== 'none'; | ||
|
|
||
| return ( | ||
| <View style={styles.container}> | ||
| <Text style={styles.title}>FormSheet Background</Text> | ||
|
|
||
| <View style={styles.controlsContainer}> | ||
| <Text style={styles.controlsLabel}>Select Background Type:</Text> | ||
| <View style={styles.buttonRow}> | ||
| {(['none', 'solid', 'composed', 'image'] as BackgroundType[]).map( | ||
| type => ( | ||
| <TouchableOpacity | ||
| key={type} | ||
| style={[ | ||
| styles.optionButton, | ||
| bgType === type && styles.optionButtonActive, | ||
| ]} | ||
| onPress={() => setBgType(type)}> | ||
| <Text | ||
| style={[ | ||
| styles.optionText, | ||
| bgType === type && styles.optionTextActive, | ||
| ]}> | ||
| {type.toUpperCase()} | ||
| </Text> | ||
| </TouchableOpacity> | ||
| ), | ||
| )} | ||
| </View> | ||
| </View> | ||
|
|
||
| <View style={styles.spacing} /> | ||
|
|
||
| <Button | ||
| title="Open FormSheet" | ||
| color={Colors.primary} | ||
| onPress={() => setIsOpen(true)} | ||
| /> | ||
|
|
||
| <FormSheet | ||
| isOpen={isOpen} | ||
| onNativeDismiss={handleDismiss} | ||
| detents='fitToContents' | ||
| backgroundComponent={renderBackground()}> | ||
| <View style={styles.sheetContent}> | ||
| <Text style={[styles.sheetTitle, isDarkBg && styles.textWhite]}> | ||
| Custom Background | ||
| </Text> | ||
|
|
||
| <Text style={[styles.description, isDarkBg && styles.textWhite]}> | ||
| Currently displaying: {bgType.toUpperCase()}. The background should | ||
| stretch to fill the entire native sheet, including the bottom safe | ||
| area. | ||
| </Text> | ||
|
|
||
| <View style={styles.spacing} /> | ||
|
|
||
| <Button | ||
| title="Dismiss from JS" | ||
| color={isDarkBg ? Colors.White : Colors.primary} | ||
| onPress={handleDismiss} | ||
| /> | ||
| </View> | ||
| </FormSheet> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| backgroundColor: Colors.offBackground, | ||
| }, | ||
| title: { | ||
| fontSize: 20, | ||
| fontWeight: 'bold', | ||
| marginBottom: 20, | ||
| color: Colors.text, | ||
| }, | ||
| controlsContainer: { | ||
| paddingHorizontal: 16, | ||
| width: '100%', | ||
| alignItems: 'center', | ||
| }, | ||
| controlsLabel: { | ||
| fontSize: 14, | ||
| color: Colors.text, | ||
| marginBottom: 8, | ||
| }, | ||
| buttonRow: { | ||
| flexDirection: 'row', | ||
| flexWrap: 'wrap', | ||
| justifyContent: 'center', | ||
| gap: 8, | ||
| }, | ||
| optionButton: { | ||
| paddingVertical: 6, | ||
| paddingHorizontal: 12, | ||
| borderRadius: 16, | ||
| borderWidth: 1, | ||
| borderColor: Colors.primary, | ||
| }, | ||
| optionButtonActive: { | ||
| backgroundColor: Colors.primary, | ||
| }, | ||
| optionText: { | ||
| fontSize: 12, | ||
| color: Colors.primary, | ||
| fontWeight: '600', | ||
| }, | ||
| optionTextActive: { | ||
| color: Colors.White, | ||
| }, | ||
| sheetContent: { | ||
| padding: 24, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| }, | ||
| sheetTitle: { | ||
| fontSize: 22, | ||
| fontWeight: '600', | ||
| marginBottom: 12, | ||
| color: Colors.text, | ||
| }, | ||
| description: { | ||
| fontSize: 16, | ||
| textAlign: 'center', | ||
| color: Colors.text, | ||
| paddingHorizontal: 16, | ||
| }, | ||
| textWhite: { | ||
| color: Colors.White, | ||
| textShadowColor: Colors.NavyLightTransparent, | ||
| textShadowOffset: { width: 0, height: 1 }, | ||
| textShadowRadius: 2, | ||
| }, | ||
| spacing: { | ||
| height: 24, | ||
| }, | ||
| composedTopBar: { | ||
| position: 'absolute', | ||
| top: 0, | ||
| left: 0, | ||
| right: 0, | ||
| height: 12, | ||
| backgroundColor: Colors.GreenLight100, | ||
| }, | ||
| composedBottomBar: { | ||
| position: 'absolute', | ||
| bottom: 0, | ||
| left: 0, | ||
| right: 0, | ||
| height: 40, | ||
| backgroundColor: Colors.RedLight100, | ||
| }, | ||
| }); | ||
|
|
||
| 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: 'Background component', | ||
| key: 'test-form-sheet-background-component-ios', | ||
| details: | ||
| 'Allows to test the custom background component rendering of the FormSheet.', | ||
| platforms: ['ios'], | ||
| e2eCoverage: 'tbd', | ||
| smokeTest: false, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Test Scenario: Background component | ||
|
|
||
| ## Details | ||
|
|
||
| **Description:** Verify the `backgroundComponent` functionality of the `FormSheet` component. This test ensures that the custom background component correctly renders beneath the content layer and fully extends to cover the entire native bounds of the sheet, including the un-collapsible bottom safe area injected by UIKit. | ||
|
|
||
| **OS test creation version:** iOS: 18.6 and 26.4 | ||
|
|
||
| ## E2E test | ||
|
|
||
| Other: Planned, but will be implemented separately. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - iOS device or simulator | ||
|
|
||
| ## Steps | ||
|
|
||
| ### Baseline | ||
|
|
||
| 1. Launch the app and navigate to the **Background component** screen. | ||
|
|
||
| - [ ] Expected: Content with the button "Open FormSheet" and background selection controls is shown. | ||
|
|
||
| --- | ||
|
|
||
| ### Verification: "NONE" Background | ||
|
|
||
| 2. Select the "NONE" background type and tap the "Open FormSheet" button. | ||
|
|
||
| - [ ] Expected: The FormSheet opens smoothly. | ||
| - [ ] Expected: The background is the default system color (e.g., white or dark depending on the theme). | ||
|
Comment on lines
+31
to
+32
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should duplicate the Expected field. We could either combine both points into a single sentence, or list them as bullets under one Expected label, like this:
OR
Either way, I think we should align on one convention as a team so we have a clear reference going forward.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will follow the second approach and omit "Expected:". It is redundant, as we can assume that any checkbox under a step represents the expected behavior to be verified.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed structure updated in scenario.md file 0afd645 and RFC https://github.com/software-mansion/react-native-screens-labs/pull/1493/changes/50448fce2fd1447f826c07d036f09a9a53c80fcb |
||
|
|
||
| 3. Tap the "Dismiss from JS" button. | ||
|
|
||
| - [ ] Expected: The FormSheet dismisses smoothly. | ||
|
|
||
| --- | ||
|
|
||
| ### Verification: "SOLID" Background | ||
|
|
||
| 4. Select the "SOLID" background type and tap the "Open FormSheet" button. | ||
|
|
||
| - [ ] Expected: The FormSheet opens smoothly. | ||
| - [ ] Expected: A solid purple background is visible behind the text content. | ||
| - [ ] Expected: The purple background extends all the way to the absolute bottom edge of the screen. | ||
|
|
||
| 5. Tap the "Dismiss from JS" button. | ||
|
|
||
| - [ ] Expected: The FormSheet dismisses smoothly. | ||
|
|
||
| --- | ||
|
|
||
| ### Verification: "COMPOSED" Background | ||
|
|
||
| 6. Select the "COMPOSED" background type and tap the "Open FormSheet" button. | ||
|
|
||
| - [ ] Expected: The FormSheet opens smoothly. | ||
| - [ ] Expected: A navy background is visible behind the text content. | ||
| - [ ] Expected: A thin green bar is visible at the top edge. | ||
| - [ ] Expected: A red bar is visible at the very bottom edge of the screen, successfully rendering underneath the navigation bar. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The navigation bar disappears after some time so referencing it here might be a bit confusing.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same below. |
||
|
|
||
| 7. Tap the "Dismiss from JS" button. | ||
|
|
||
| - [ ] Expected: The FormSheet dismisses smoothly. | ||
|
|
||
| --- | ||
|
|
||
| ### Verification: "IMAGE" Background | ||
|
|
||
| 8. Select the "IMAGE" background type and tap the "Open FormSheet" button. | ||
|
|
||
| - [ ] Expected: The FormSheet opens smoothly. | ||
| - [ ] Expected: An image background is visible behind the text content. | ||
| - [ ] Expected: The image fully covers the native sheet bounds, extending completely under the navigation bar. | ||
|
|
||
| 9. Tap the "Dismiss from JS" button. | ||
|
|
||
| - [ ] Expected: The FormSheet dismisses smoothly and returns the user to the underlying main screen. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following new naming (described in RFC), if we are planning to implement e2e test I would change this line to: