-
-
Notifications
You must be signed in to change notification settings - Fork 598
feat(iOS, Tabs): add bottomAccessory support #3288
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
Open
kligarski
wants to merge
63
commits into
main
Choose a base branch
from
@kligarski/bottom-accessory
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 58 commits
Commits
Show all changes
63 commits
Select commit
Hold shift + click to select a range
d8eaeae
add skeleton
kligarski 226954c
WIP
kligarski 28ac025
controlers
kligarski a71d92b
helper and wrapper, invalidating display link
kligarski 6a9c2a1
refactor
kligarski 5ea44e8
refactor 2
kligarski 775ae70
invalidating skeleton
kligarski a4a84e6
don't update if tab controllers did not change
kligarski 7e608c4
use correct type in RCTInvalidating's invalidate
kligarski 4df66d2
add environment change events
kligarski 1f8b2da
ios 26 availability, typo
kligarski 35aae34
refactor JS API
kligarski 537adad
use KVO for native bottom accessory view's frame
kligarski 7eb5f64
Paper implementation
kligarski bea9501
add test screen
kligarski 9bead07
revert changes to TestBottomTabs
kligarski 33bfde5
remove custom wrapper
kligarski 4ba4a5c
fix inserting and removing react subviews in host component
kligarski cac1c98
restore App.tsx
kligarski bdaada7
Merge branch 'main' into @kligarski/bottom-accessory
kligarski aadf116
in-code docs refinement
kligarski 1c34da9
add iOS version guards
kligarski 31c3bef
fix guards
kligarski a58c723
fix build on Paper
kligarski 6b213bd
Merge branch 'main' into @kligarski/bottom-accessory
kligarski 28ee322
adapt test screen to new icons API
kligarski 44f4c5f
suggestions from code review part 1
kligarski 5ca50ed
fix renamed prop for Paper
kligarski 2c69f3e
extract UITabAccessoryEnvironment conversion
kligarski 1696be4
remove duplication in mounting/unmounting code
kligarski 5f83151
remove redundant flex
kligarski 2f5d7c3
use self-closing tag
kligarski e98390a
unify naming for content offset
kligarski 946b546
use ifNeeded suffix
kligarski cdb7d84
import type JS
kligarski 65524d8
refactor handling subview change in bottom tabs host
kligarski 59cee3a
add missing guards
kligarski 99e2a54
Merge branch 'main' into @kligarski/bottom-accessory
kligarski 397d175
view swap approach PoC
kligarski e19e143
cleanup
kligarski 9e2a8f8
maybe fix build
kligarski 0b3af56
cleanup
kligarski b9d1bca
update docs
kligarski 7ab2cb4
ifdef RCT_NEW_ARCH_ENABLED -> if RCT_NEW_ARCH_ENABLED
kligarski fceb14f
Merge branch 'main' into @kligarski/bottom-accessory
kligarski 3241429
use weak pointer from helper to accessory view
kligarski 7d8bbaf
rename notifyFrameUpdate -> notifyWrapperViewFrameHasChanged
kligarski 48360ce
move state to component view, update invalidate fn
kligarski f366094
simplify guards
kligarski 0a96a35
legacy arch only view manager
kligarski f13c54b
add BOTTOM_ACCESSORY_AVAILABLE macro
kligarski eb0dcf3
log error if child is not screen or accessory
kligarski c8f8d3d
add hasModifiedReactSubviewsInCurrentTransaction as property in exten…
kligarski cc84e2a
rename react subview validation method
kligarski efba719
use StyleSheet.absoluteFill for bottom accessory JS components
kligarski 6aade86
fix build on Paper
kligarski cf4e24c
handle _reactSubviews inside validateAndHandleReactSubview
kligarski 0bc2cb8
extract shadow updates to separate class
kligarski b521658
include -> import
kligarski 2863781
use hasModifiedReactSubviewsInCurrentTransaction
kligarski c55db8d
move bottom accessory availability macro to RNSDefines
kligarski 4929f34
add comment explaining why we call handleContentViewVisibility... in …
kligarski 487ec07
simplify environment conversion for event emitters
kligarski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,286 @@ | ||
| import React, { | ||
| createContext, | ||
| Dispatch, | ||
| SetStateAction, | ||
| useContext, | ||
| useState, | ||
| } from 'react'; | ||
|
|
||
| import ConfigWrapperContext, { | ||
| type Configuration, | ||
| DEFAULT_GLOBAL_CONFIGURATION, | ||
| } from '../shared/gamma/containers/bottom-tabs/ConfigWrapperContext'; | ||
| import { | ||
| BottomTabsContainer, | ||
| type TabConfiguration, | ||
| } from '../shared/gamma/containers/bottom-tabs/BottomTabsContainer'; | ||
| import { | ||
| ColorValue, | ||
| Pressable, | ||
| PressableProps, | ||
| ScrollView, | ||
| StyleSheet, | ||
| Text, | ||
| View, | ||
| } from 'react-native'; | ||
| import { SettingsPicker, SettingsSwitch } from '../shared'; | ||
| import Colors from '../shared/styling/Colors'; | ||
| import PressableWithFeedback from '../shared/PressableWithFeedback'; | ||
| import { TabBarMinimizeBehavior } from 'react-native-screens'; | ||
| import { BottomTabsAccessoryEnvironment } from 'react-native-screens/components/bottom-tabs/BottomTabsAccessory.types'; | ||
| import { NavigationContainer } from '@react-navigation/native'; | ||
|
|
||
| type BottomAccessoryConfig = { | ||
| shown: boolean; | ||
| backgroundColor: ColorValue; | ||
| shouldAdaptToEnvironment: boolean; | ||
| tabBarMinimizeBehavior: TabBarMinimizeBehavior; | ||
| }; | ||
|
|
||
| type BottomAccessoryContextInterface = { | ||
| config: BottomAccessoryConfig; | ||
| setConfig: Dispatch<SetStateAction<BottomAccessoryConfig>>; | ||
| }; | ||
|
|
||
| const DEFAULT_BOTTOM_ACCESSORY_CONFIG: BottomAccessoryConfig = { | ||
| shown: true, | ||
| backgroundColor: 'transparent', | ||
| shouldAdaptToEnvironment: true, | ||
| tabBarMinimizeBehavior: 'onScrollDown', | ||
| }; | ||
|
|
||
| const BottomAccessoryContext = | ||
| createContext<BottomAccessoryContextInterface | null>(null); | ||
|
|
||
| const useBottomAccessoryContext = () => { | ||
| const bottomAccessoryContext = useContext(BottomAccessoryContext); | ||
|
|
||
| if (!bottomAccessoryContext) { | ||
| throw new Error( | ||
| 'useBottomAccessoryContext has to be used within <BottomAccessoryContext.Provider>', | ||
| ); | ||
| } | ||
|
|
||
| return bottomAccessoryContext; | ||
| }; | ||
|
|
||
| function Config() { | ||
| const { config, setConfig } = useBottomAccessoryContext(); | ||
|
|
||
| return ( | ||
| <ScrollView contentContainerStyle={{ padding: 16, gap: 5 }}> | ||
| <SettingsSwitch | ||
| label="shown" | ||
| value={config.shown} | ||
| onValueChange={value => setConfig({ ...config, shown: value })} | ||
| /> | ||
| <SettingsPicker<string> | ||
| label="backgroundColor" | ||
| value={String(config.backgroundColor)} | ||
| onValueChange={value => | ||
| setConfig({ | ||
| ...config, | ||
| backgroundColor: value, | ||
| }) | ||
| } | ||
| items={['transparent', Colors.NavyLightTransparent, Colors.BlueLight80]} | ||
| /> | ||
| <SettingsSwitch | ||
| label="shouldAdaptToEnvironment" | ||
| value={config.shouldAdaptToEnvironment} | ||
| onValueChange={value => | ||
| setConfig({ ...config, shouldAdaptToEnvironment: value }) | ||
| } | ||
| /> | ||
| <SettingsPicker<TabBarMinimizeBehavior> | ||
| label="tabBarMinimizeBehavior" | ||
| value={config.tabBarMinimizeBehavior} | ||
| onValueChange={value => | ||
| setConfig({ | ||
| ...config, | ||
| tabBarMinimizeBehavior: value, | ||
| }) | ||
| } | ||
| items={['automatic', 'onScrollDown', 'onScrollUp', 'never']} | ||
| /> | ||
| </ScrollView> | ||
| ); | ||
| } | ||
|
|
||
| function TestScreen() { | ||
| return ( | ||
| <ScrollView | ||
| contentContainerStyle={{ | ||
| width: '100%', | ||
| height: 'auto', | ||
| gap: 15, | ||
| paddingHorizontal: 30, | ||
| }}> | ||
| {[...Array(50).keys()].map(index => ( | ||
| <PressableWithFeedback | ||
| key={index + 1} | ||
| onPress={() => console.log(`Pressed #${index + 1}`)} | ||
| style={{ | ||
| paddingVertical: 10, | ||
| paddingHorizontal: 20, | ||
| }}> | ||
| <Text>Pressable #{index + 1}</Text> | ||
| </PressableWithFeedback> | ||
| ))} | ||
| </ScrollView> | ||
| ); | ||
| } | ||
|
|
||
| const TAB_CONFIGS: TabConfiguration[] = [ | ||
| { | ||
| tabScreenProps: { | ||
| tabKey: 'Tab1', | ||
| title: 'Config', | ||
| icon: { | ||
| ios: { | ||
| type: 'sfSymbol', | ||
| name: 'gear', | ||
| }, | ||
| }, | ||
| }, | ||
| component: Config, | ||
| }, | ||
| { | ||
| tabScreenProps: { | ||
| tabKey: 'Tab2', | ||
| title: 'Test', | ||
| icon: { | ||
| ios: { | ||
| type: 'sfSymbol', | ||
| name: 'rectangle.stack', | ||
| }, | ||
| }, | ||
| }, | ||
| component: TestScreen, | ||
| }, | ||
| ]; | ||
|
|
||
| function getBottomAccessory( | ||
| environment: BottomTabsAccessoryEnvironment, | ||
| config: BottomAccessoryConfig, | ||
| ) { | ||
| const pressableStyle: PressableProps['style'] = ({ pressed }) => ({ | ||
| shadowColor: '#000', | ||
| shadowOffset: { | ||
| width: 0.5, | ||
| height: 0.5, | ||
| }, | ||
| shadowOpacity: pressed ? 0.9 : 0.0, | ||
| shadowRadius: 2, | ||
| transform: pressed ? [{ scale: 1.1 }] : [], | ||
| }); | ||
| return ( | ||
| <View | ||
| style={[ | ||
| styles.container, | ||
| { | ||
| backgroundColor: config.backgroundColor, | ||
| }, | ||
| ]}> | ||
| <View style={styles.left}> | ||
| <View style={styles.cover} /> | ||
| <View style={styles.data}> | ||
| <Text style={styles.title} numberOfLines={1}> | ||
| Never Gonna Give You Up | ||
| </Text> | ||
| <Text style={styles.author}>Rick Astley</Text> | ||
| </View> | ||
| </View> | ||
|
|
||
| <View style={styles.right}> | ||
| {(environment === 'regular' || !config.shouldAdaptToEnvironment) && ( | ||
| <Pressable | ||
| onPress={() => console.log('You know the rules and so do I')} | ||
| style={pressableStyle}> | ||
| <Text style={{ fontSize: 28 }}>♫</Text> | ||
| </Pressable> | ||
| )} | ||
|
|
||
| <Pressable | ||
| onPress={() => console.log("We're no strangers to love")} | ||
| style={pressableStyle}> | ||
| <Text style={{ fontSize: 30 }}>▶</Text> | ||
| </Pressable> | ||
| </View> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| function Tabs() { | ||
| const [config, setConfig] = React.useState<Configuration>( | ||
| DEFAULT_GLOBAL_CONFIGURATION, | ||
| ); | ||
|
|
||
| const { config: bottomAccessoryConfig } = useBottomAccessoryContext(); | ||
|
|
||
| return ( | ||
| <ConfigWrapperContext.Provider | ||
| value={{ | ||
| config, | ||
| setConfig, | ||
| }}> | ||
| <BottomTabsContainer | ||
| tabConfigs={TAB_CONFIGS} | ||
| tabBarMinimizeBehavior={bottomAccessoryConfig.tabBarMinimizeBehavior} | ||
| bottomAccessory={ | ||
| bottomAccessoryConfig.shown | ||
| ? environment => | ||
| getBottomAccessory(environment, bottomAccessoryConfig) | ||
| : undefined | ||
| } | ||
| /> | ||
| </ConfigWrapperContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| function App() { | ||
| const [bottomAccessoryConfig, setBottomAccessoryConfig] = | ||
| useState<BottomAccessoryConfig>(DEFAULT_BOTTOM_ACCESSORY_CONFIG); | ||
|
|
||
| return ( | ||
| <NavigationContainer> | ||
| <BottomAccessoryContext.Provider | ||
| value={{ | ||
| config: bottomAccessoryConfig, | ||
| setConfig: setBottomAccessoryConfig, | ||
| }}> | ||
| <Tabs /> | ||
| </BottomAccessoryContext.Provider> | ||
| </NavigationContainer> | ||
| ); | ||
| } | ||
|
|
||
| export default App; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| flexDirection: 'row', | ||
| justifyContent: 'space-between', | ||
| alignItems: 'center', | ||
| padding: 5, | ||
| }, | ||
| left: { | ||
| flex: 1, | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| gap: 10, | ||
| marginLeft: 10, | ||
| }, | ||
| cover: { backgroundColor: 'pink', width: 30, height: 30 }, | ||
| data: { flex: 1, paddingRight: 5 }, | ||
| title: { fontWeight: 'bold' }, | ||
| author: { fontSize: 10, color: 'gray' }, | ||
| right: { | ||
| flex: 0, | ||
| flexDirection: 'row-reverse', | ||
| alignItems: 'center', | ||
| gap: 12, | ||
| paddingRight: 10, | ||
| }, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
common/cpp/react/renderer/components/rnscreens/RNSBottomTabsAccessoryComponentDescriptor.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| #pragma once | ||
|
|
||
| #include <react/debug/react_native_assert.h> | ||
| #include <react/renderer/components/rnscreens/Props.h> | ||
| #include <react/renderer/core/ConcreteComponentDescriptor.h> | ||
| #include "RNSBottomTabsAccessoryShadowNode.h" | ||
|
|
||
| namespace facebook::react { | ||
|
|
||
| class RNSBottomTabsAccessoryComponentDescriptor final | ||
| : public ConcreteComponentDescriptor<RNSBottomTabsAccessoryShadowNode> { | ||
| public: | ||
| using ConcreteComponentDescriptor::ConcreteComponentDescriptor; | ||
|
|
||
| void adopt(ShadowNode &shadowNode) const override { | ||
| react_native_assert( | ||
| dynamic_cast<RNSBottomTabsAccessoryShadowNode *>(&shadowNode)); | ||
| auto &bottomTabsAccessoryShadowNode = | ||
| static_cast<RNSBottomTabsAccessoryShadowNode &>(shadowNode); | ||
|
|
||
| auto state = std::static_pointer_cast< | ||
| const RNSBottomTabsAccessoryShadowNode::ConcreteState>( | ||
| shadowNode.getState()); | ||
| auto stateData = state->getData(); | ||
|
|
||
| if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { | ||
kkafar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| bottomTabsAccessoryShadowNode.setSize(stateData.frameSize); | ||
| } | ||
|
|
||
| ConcreteComponentDescriptor::adopt(shadowNode); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace facebook::react | ||
14 changes: 14 additions & 0 deletions
14
common/cpp/react/renderer/components/rnscreens/RNSBottomTabsAccessoryShadowNode.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| #include "RNSBottomTabsAccessoryShadowNode.h" | ||
|
|
||
| namespace facebook::react { | ||
|
|
||
| extern const char RNSBottomTabsAccessoryComponentName[] = | ||
| "RNSBottomTabsAccessory"; | ||
|
|
||
| Point RNSBottomTabsAccessoryShadowNode::getContentOriginOffset( | ||
| bool /*includeTransform*/) const { | ||
| auto stateData = getStateData(); | ||
| return stateData.contentOffset; | ||
| } | ||
|
|
||
| } // namespace facebook::react |
26 changes: 26 additions & 0 deletions
26
common/cpp/react/renderer/components/rnscreens/RNSBottomTabsAccessoryShadowNode.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| #pragma once | ||
|
|
||
| #include <jsi/jsi.h> | ||
| #include <react/renderer/components/rnscreens/EventEmitters.h> | ||
| #include <react/renderer/components/rnscreens/Props.h> | ||
| #include <react/renderer/components/view/ConcreteViewShadowNode.h> | ||
| #include "RNSBottomTabsAccessoryState.h" | ||
|
|
||
| namespace facebook::react { | ||
|
|
||
| JSI_EXPORT extern const char RNSBottomTabsAccessoryComponentName[]; | ||
|
|
||
| class JSI_EXPORT RNSBottomTabsAccessoryShadowNode final | ||
| : public ConcreteViewShadowNode< | ||
| RNSBottomTabsAccessoryComponentName, | ||
| RNSBottomTabsAccessoryProps, | ||
| RNSBottomTabsAccessoryEventEmitter, | ||
| RNSBottomTabsAccessoryState> { | ||
| public: | ||
| using ConcreteViewShadowNode::ConcreteViewShadowNode; | ||
| using StateData = ConcreteViewShadowNode::ConcreteStateData; | ||
|
|
||
| Point getContentOriginOffset(bool includeTransform) const override; | ||
| }; | ||
|
|
||
| } // namespace facebook::react |
20 changes: 20 additions & 0 deletions
20
common/cpp/react/renderer/components/rnscreens/RNSBottomTabsAccessoryState.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #pragma once | ||
|
|
||
| #include <react/renderer/graphics/Point.h> | ||
| #include <react/renderer/graphics/Size.h> | ||
|
|
||
| namespace facebook::react { | ||
|
|
||
| class JSI_EXPORT RNSBottomTabsAccessoryState final { | ||
| public: | ||
| using Shared = std::shared_ptr<const RNSBottomTabsAccessoryState>; | ||
|
|
||
| RNSBottomTabsAccessoryState() {}; | ||
| RNSBottomTabsAccessoryState(Size frameSize_, Point contentOffset_) | ||
| : frameSize(frameSize_), contentOffset(contentOffset_) {}; | ||
|
|
||
| const Size frameSize{}; | ||
| const Point contentOffset{}; | ||
| }; | ||
|
|
||
| } // namespace facebook::react |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.