Skip to content
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

feat(iOS): support for custom detents with formSheet presentation #1852

Closed
wants to merge 74 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
65a3ac1
Add PoC
kkafar Aug 1, 2023
894c45a
Reorganise native code & rename the prop to sheetCustomDetents
kkafar Aug 2, 2023
d04c354
Set default for `sheetCustomDetents` in `InnerScreen`
kkafar Aug 2, 2023
1f9499e
Pass the new prop through NativeStackView
kkafar Aug 2, 2023
0fcad06
Remove debug logging
kkafar Aug 2, 2023
bd556e0
Add stubs for type documentation
kkafar Aug 2, 2023
9c2636f
Cleanup native code a bit
kkafar Aug 2, 2023
1ce34bc
Rename updatePresentationStyle to updateFormSheetPresentationStyle
kkafar Aug 2, 2023
3db72e7
Add dev dependency on `jotai`
kkafar Aug 2, 2023
b34b6e5
Refactor test example using `jotai`
kkafar Aug 2, 2023
fe5e946
Indentation
kkafar Aug 2, 2023
9ade339
Add custom as detent type
kkafar Aug 2, 2023
4ab9444
Add 'custom' to Screen component fabric spec
kkafar Aug 2, 2023
b5a42f2
Update example in FTE
kkafar Aug 2, 2023
d03af70
Start implementing Fabric
kkafar Aug 2, 2023
299e600
Add sheetCustomDetents to Screen codegen spec
kkafar Aug 2, 2023
23901dc
Set sheetCustomDetents only when new array is different
kkafar Aug 2, 2023
c2ddf39
TOREVERT: temporarily remove updating shadow node state after layouti…
kkafar Aug 3, 2023
5a8c934
Start playing around with new API in NativeStackView
kkafar Aug 3, 2023
01af2f6
Filter numbers
kkafar Aug 4, 2023
f9ad05d
Stash changes
kkafar Aug 7, 2023
35039c1
Change `sheetAllowedDetents` prop type to `SheetDetentTypes | number[]`
kkafar Aug 8, 2023
97aebd4
Refactor native code
kkafar Aug 8, 2023
c1d822b
Update example in TE
kkafar Aug 8, 2023
7b9af0b
Cleanup logic in v5 impl a bit
kkafar Aug 8, 2023
eebbc59
Do not declare properties on category
kkafar Aug 9, 2023
715e7b5
custom largest undimmed detent
kkafar Aug 9, 2023
795da38
Improve native logic
kkafar Aug 9, 2023
016e5d0
Split sheetLargestUndimmedDetent into two props on JS side
kkafar Aug 9, 2023
bcb2aa9
Update example in TE
kkafar Aug 9, 2023
4d9db14
Add docs
kkafar Aug 9, 2023
f5d501c
Missing newline
kkafar Aug 9, 2023
0e6f9a6
Adjust Fabric implementations
kkafar Aug 9, 2023
97e5f4e
Update native screen spec
kkafar Aug 9, 2023
25bd874
Add missing docs
kkafar Aug 9, 2023
0048827
Cleanup tests
kkafar Aug 9, 2023
dd45717
Rename arrayFromVector method to NSNumberMutableArrayFromFloatVector
kkafar Aug 9, 2023
77e98d2
Refactor detentsFromSnapPoints method
kkafar Aug 9, 2023
46c5676
Remove unused ClampedNumber type
kkafar Aug 9, 2023
6a6693c
Stubs for Android
kkafar Aug 9, 2023
8d134d2
Spelling 1
kkafar Aug 9, 2023
41123c9
Spelling 2
kkafar Aug 9, 2023
77b9e26
Improve example logic a bit
kkafar Aug 11, 2023
9a8eb64
Remove RNS Array utility
kkafar Aug 22, 2023
3ba3d43
Restore initial `updateBounds` implementation on Fabric
kkafar Aug 22, 2023
c74b2d1
fix(iOS): modal view flickering (#1870)
kkafar Nov 15, 2023
15f9c5c
Merge branch 'main' into @kkafar/custom-detents
kkafar Dec 1, 2023
73ed7f8
Patch clipping with displaylink & background colour
kkafar Dec 13, 2023
3952428
Temporarily set background on NativeStackView
kkafar Dec 13, 2023
4009e90
Properly pause/unpause the displaylink
kkafar Dec 13, 2023
eec733d
Start cleaning up
kkafar Dec 13, 2023
3edede5
Cleanup updateBounds, animationDidStart, animationDidStop
kkafar Dec 13, 2023
2f4743f
Update jotai
kkafar Dec 13, 2023
597d082
Remove setFrame & setBounds methods
kkafar Dec 13, 2023
59008f8
Unify example between platforms
kkafar Dec 13, 2023
90b2639
Remove fixed backgorund style from screen component
kkafar Dec 14, 2023
c9ffb76
Do not use custom shadow view (yet)
kkafar Dec 14, 2023
abd6c03
Properly call updateFormSheetPresentationStyle
kkafar Dec 14, 2023
b8abf7b
Cleanup updateFormSheetPresentation code & animate prop value changes
kkafar Dec 15, 2023
a7bd623
REVERT: Stash debug setup
kkafar Dec 15, 2023
a8015a9
Flickering sheet fix v30 (partial)
kkafar Jan 26, 2024
282eacc
Try to fix flickering with setting full height for view when dragging up
kkafar Jan 30, 2024
5cc8e41
iOS code for avoiding reactSetFrame on screens subviews
kkafar Feb 1, 2024
6be17f2
JS code to avoid reactSetFrame on screens content when formSheet is i…
kkafar Feb 1, 2024
6b33387
Update example in TE
kkafar Feb 1, 2024
8233c3f
Allow for passing screen style (naively, NEED TO FILTER THIS)
kkafar Feb 1, 2024
7e2b131
Patch for scrollview
kkafar Feb 1, 2024
d0800d1
Cleanup 1
kkafar Feb 2, 2024
9f6f0a9
Cleanup 2
kkafar Feb 2, 2024
5ac1072
Cleanup 3
kkafar Feb 2, 2024
52c6353
Add important comments on the reasons why the code looks like it does
kkafar Feb 2, 2024
0391257
Remove custom shadow view code
kkafar Feb 2, 2024
9af41ba
Improve KVO observer handling to balance out add/remove calls
kkafar Feb 2, 2024
1ae540e
Update TE
kkafar Feb 5, 2024
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
1 change: 1 addition & 0 deletions FabricTestExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jotai": "^2.6.0",
"jest": "^29.6.3",
"prettier": "^2.8.8",
"react-test-renderer": "18.2.0",
Expand Down
202 changes: 133 additions & 69 deletions FabricTestExample/src/Test1649.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
import * as React from 'react';
import { Button, StyleSheet, View, Text, ScrollView } from 'react-native';
import {
Button,
StyleSheet,
View,
Text,
ScrollView,
TextInput,
} from 'react-native';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
NativeStackNavigationOptions,
} from 'react-native-screens/native-stack';
import { SheetDetentTypes } from 'react-native-screens';
import * as jotai from 'jotai';

type SheetDetent = NativeStackNavigationOptions['sheetAllowedDetents'];
type SheetUndimmedDetent =
NativeStackNavigationOptions['sheetLargestUndimmedDetent'];

type NavProp = {
navigation: NativeStackNavigationProp<ParamListBase>;
};

const Stack = createNativeStackNavigator();

const initialAllowedDetentsArray = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];

/// Sheet options
// const allowedDetentsAtom = jotai.atom<SheetDetent>('all');
// const largestUndimmedDetentAtom = jotai.atom<SheetDetentTypes | number>('all');

const allowedDetentsAtom = jotai.atom<SheetDetent>(initialAllowedDetentsArray);
const largestUndimmedDetentAtom = jotai.atom<SheetUndimmedDetent>(3);

// const allowedDetentsAtom = jotai.atom<SheetDetent>([0.7]);
// const largestUndimmedDetentAtom = jotai.atom<SheetDetentTypes | number>(0);

const grabberVisibleAtom = jotai.atom(false);
const cornerRadiusAtom = jotai.atom(-1);
const expandsWhenScrolledToEdgeAtom = jotai.atom(false);

const sheetOptionsAtom = jotai.atom(get => ({
sheetAllowedDetents: get(allowedDetentsAtom),
sheetLargestUndimmedDetent: get(largestUndimmedDetentAtom),
sheetGrabberVisible: get(grabberVisibleAtom),
sheetCornerRadius: get(cornerRadiusAtom),
sheetExpandsWhenScrolledToEdge: get(expandsWhenScrolledToEdgeAtom),
}));

export default function App(): JSX.Element {
const sheetOptions = jotai.useAtomValue(sheetOptionsAtom);

const initialScreenOptions: NativeStackNavigationOptions = {
stackPresentation: 'formSheet',
sheetAllowedDetents: 'all',
sheetLargestUndimmedDetent: 'medium',
sheetGrabberVisible: false,
sheetCornerRadius: -1,
sheetExpandsWhenScrolledToEdge: true,
...sheetOptions,
};

return (
Expand All @@ -31,7 +68,7 @@ export default function App(): JSX.Element {
headerShown: true,
headerHideBackButton: false,
}}>
<Stack.Screen name="First" component={First} />
<Stack.Screen name="Home" component={Home} />
<Stack.Screen
name="Second"
component={Second}
Expand All @@ -53,16 +90,19 @@ export default function App(): JSX.Element {
...initialScreenOptions,
}}
/>
<Stack.Screen
name="SheetScreenWithTextInput"
component={SheetScreenWithTextInput}
options={{
...initialScreenOptions,
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

function First({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
function Home({ navigation }: NavProp) {
return (
<Button
title="Tap me for the second screen"
Expand All @@ -71,11 +111,7 @@ function First({
);
}

function Second({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
function Second({ navigation }: NavProp) {
return (
<>
<Button
Expand All @@ -86,41 +122,63 @@ function Second({
title="Open the sheet with ScrollView"
onPress={() => navigation.navigate('SheetScreenWithScrollView')}
/>
<Button
title="Open the sheet with text input (keyboard interaction)"
onPress={() => navigation.navigate('SheetScreenWithTextInput')}
/>
</>
);
}

function SheetScreen({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
const [radius, setRadius] = React.useState(-1);
const [detent, setDetent] = React.useState<SheetDetentTypes>('all');
const [largestUndimmedDetent, sheetLargestUndimmedDetent] =
React.useState<SheetDetentTypes>('all');
const [isGrabberVisible, setIsGrabberVisible] = React.useState(false);
// navigation
const [shouldExpand, setShouldExpand] = React.useState(true);

function nextDetentLevel(currDetent: SheetDetentTypes): SheetDetentTypes {
if (currDetent === 'all') {
function SheetScreen({ navigation }: NavProp) {
const [allowedDetents, setAllowedDetents] = jotai.useAtom(allowedDetentsAtom);
const [largestUndimmedDetent, setLargestUndimmedDetent] = jotai.useAtom(
largestUndimmedDetentAtom,
);
const [grabberVisible, setGrabberVisible] = jotai.useAtom(grabberVisibleAtom);
const [cornerRadius, setCornerRadius] = jotai.useAtom(cornerRadiusAtom);
const [expandsWhenScrolledToEdge, setExpandsWhenScrolledToEdge] =
jotai.useAtom(expandsWhenScrolledToEdgeAtom);

function nextDetentLevel(detent: SheetDetent): SheetDetent {
if (Array.isArray(detent)) {
return 'all';
} else if (detent === 'all') {
return 'medium';
} else if (currDetent === 'medium') {
} else if (detent === 'medium') {
return 'large';
} else if (currDetent === 'large') {
return 'all';
} else if (detent === 'large') {
return initialAllowedDetentsArray;
} else {
console.warn('Unhandled sheetDetent type');
return 'all';
}
}

function nextUndimmedDetentLevel(
detent: SheetUndimmedDetent,
): SheetUndimmedDetent {
if (typeof detent === 'number') {
if (Array.isArray(allowedDetents) && detent + 1 < allowedDetents.length) {
return detent + 1;
} else {
return 'all';
}
} else if (detent === 'large') {
if (Array.isArray(allowedDetents)) {
return 0;
} else {
return 'all';
}
} else {
return nextDetentLevel(detent);
}
}

return (
<View style={styles.centeredView}>
<Button
title="Tap me for the first screen"
onPress={() => navigation.navigate('First')}
onPress={() => navigation.navigate('Home')}
/>
<Button
title="Tap me for the second screen"
Expand All @@ -129,66 +187,48 @@ function SheetScreen({
<Button
title="Change the corner radius"
onPress={() => {
const newRadius = radius >= 150 ? -1.0 : radius + 50;
setRadius(newRadius);
navigation.setOptions({
sheetCornerRadius: newRadius,
});
const newRadius = cornerRadius >= 150 ? -1.0 : cornerRadius + 50;
setCornerRadius(newRadius);
}}
/>
<Text>radius: {radius}</Text>
<Text>radius: {cornerRadius}</Text>
<Button
title="Change detent level"
onPress={() => {
const newDetentLevel = nextDetentLevel(detent);
setDetent(newDetentLevel);
navigation.setOptions({
sheetAllowedDetents: newDetentLevel,
});
const newDetentLevel = nextDetentLevel(allowedDetents);
setAllowedDetents(newDetentLevel);
}}
/>
<Text>detent: {detent}</Text>
<Text>detent: {allowedDetents}</Text>
<Button
title="Change largest undimmed detent"
onPress={() => {
const newDetentLevel = nextDetentLevel(largestUndimmedDetent);
sheetLargestUndimmedDetent(newDetentLevel);
navigation.setOptions({
sheetLargestUndimmedDetent: newDetentLevel,
});
const newDetentLevel = nextUndimmedDetentLevel(largestUndimmedDetent);
setLargestUndimmedDetent(newDetentLevel);
}}
/>
<Text>largestUndimmedDetent: {largestUndimmedDetent}</Text>
<Button
title="Toggle sheetExpandsWhenScrolledToEdge"
onPress={() => {
setShouldExpand(!shouldExpand);
navigation.setOptions({
sheetExpandsWhenScrolledToEdge: !shouldExpand,
});
setExpandsWhenScrolledToEdge(!expandsWhenScrolledToEdge);
}}
/>
<Text>
sheetExpandsWhenScrolledToEdge: {shouldExpand ? 'true' : 'false'}
sheetExpandsWhenScrolledToEdge:{' '}
{expandsWhenScrolledToEdge ? 'true' : 'false'}
</Text>
<Button
title="Toggle grabber visibility"
onPress={() => {
setIsGrabberVisible(!isGrabberVisible);
navigation.setOptions({
sheetGrabberVisible: !isGrabberVisible,
});
setGrabberVisible(!grabberVisible);
}}
/>
</View>
);
}

function SheetScreenWithScrollView({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
function SheetScreenWithScrollView({ navigation }: NavProp) {
return (
<>
<View style={styles.centeredView}>
Expand All @@ -203,6 +243,21 @@ function SheetScreenWithScrollView({
);
}

function SheetScreenWithTextInput({ navigation }: NavProp) {
const [textValue, setTextValue] = React.useState('text input');

return (
<View style={styles.centeredView}>
<TextInput
style={[styles.bordered, styles.keyboardTriggerTextInput]}
value={textValue}
onChangeText={text => setTextValue(text)}
/>
<SheetScreen navigation={navigation} />
</View>
);
}

const styles = StyleSheet.create({
headerView: {
height: 20,
Expand All @@ -213,4 +268,13 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
bordered: {
borderColor: 'black',
borderWidth: 2,
},
keyboardTriggerTextInput: {
paddingVertical: 5,
paddingHorizontal: 4,
marginTop: 10,
},
});
5 changes: 5 additions & 0 deletions FabricTestExample/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4671,6 +4671,11 @@ joi@^17.2.1:
"@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0"

jotai@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.6.0.tgz#68b5d634f78a9ea55adfb8d92206ef59304b5dd5"
integrity sha512-Vt6hsc04Km4j03l+Ax+Sc+FVft5cRJhqgxt6GTz6GM2eM3DyX3CdBdzcG0z2FrlZToL1/0OAkqDghIyARWnSuQ==

"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down
2 changes: 1 addition & 1 deletion TestsExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,5 @@ import Test1864 from './src/Test1864';
enableFreeze(true);

export default function App() {
return <Test42 />;
return <Test1649 />;
}
1 change: 1 addition & 0 deletions TestsExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"jotai": "^2.6.0",
"prettier": "^2.8.8",
"react-test-renderer": "18.2.0",
"typescript": "5.0.4"
Expand Down
Loading