Skip to content

Commit

Permalink
Add API for imperative actions on RNSSearchBar
Browse files Browse the repository at this point in the history
  • Loading branch information
egegunes committed Jul 9, 2022
1 parent 166d60c commit d553167
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 14 deletions.
22 changes: 20 additions & 2 deletions Example/src/screens/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useLayoutEffect, useState } from 'react';
import React, { useLayoutEffect, useRef, useState } from 'react';
import { I18nManager, ScrollView, Text, StyleSheet } from 'react-native';
import { SearchBarProps } from 'react-native-screens';
import { SearchBarRef, SearchBarProps } from 'react-native-screens';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
Expand Down Expand Up @@ -49,10 +49,12 @@ const MainScreen = ({ navigation }: MainScreenProps): JSX.Element => {
'sentences'
);
const [inputType, setInputType] = useState<InputType>('text');
const searchBarRef = useRef<SearchBarRef>(null);

useLayoutEffect(() => {
navigation.setOptions({
searchBar: {
ref: searchBarRef,
barTintColor,
hintTextColor,
headerIconColor,
Expand Down Expand Up @@ -173,6 +175,22 @@ const MainScreen = ({ navigation }: MainScreenProps): JSX.Element => {
value={shouldShowHintSearchIcon}
onValueChange={setShouldShowHintSearchIcon}
/>
<Text style={styles.heading}>Imperative actions</Text>
<Button
onPress={() => searchBarRef.current?.clearText()}
title="Clear Text"
/>
<Button
onPress={() => searchBarRef.current?.toggleCancelButton(true)}
title="Show cancel"
/>
<Button
onPress={() => searchBarRef.current?.toggleCancelButton(false)}
title="Hide cancel"
/>
<Button onPress={() => searchBarRef.current?.focus()} title="Focus" />
<Button onPress={() => searchBarRef.current?.unfocus()} title="Unfocus" />
<Button onPress={() => searchBarRef.current?.blur()} title="Blur" />
<Text style={styles.heading}>Other</Text>
<Button
onPress={() => navigation.navigate('Search')}
Expand Down
113 changes: 113 additions & 0 deletions TestsExample/src/Test1097.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as React from 'react';
import {Button, NativeSyntheticEvent, ScrollView} from 'react-native';
import {
NavigationContainer,
NavigationProp,
ParamListBase,
} from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackScreenProps,
} from 'react-native-screens/native-stack';
import {SearchBarProps} from 'react-native-screens';

const AppStack = createNativeStackNavigator();

export default function App(): JSX.Element {
return (
<NavigationContainer>
<AppStack.Navigator
screenOptions={{
headerLargeTitle: true,
headerTranslucent: true,
}}>
<AppStack.Screen name="First" component={First} />
<AppStack.Screen name="Second" component={Second} />
</AppStack.Navigator>
</NavigationContainer>
);
}

function First({navigation}: NativeStackScreenProps<ParamListBase>) {
const searchBarRef = React.useRef();

React.useEffect(() => {
navigation.setOptions({
searchBar: searchBarOptions,
});
}, [navigation]);

const [search, setSearch] = React.useState('');

const searchBarOptions: SearchBarProps = {
// @ts-ignore
ref: searchBarRef,
barTintColor: 'powderblue',
hideWhenScrolling: true,
obscureBackground: false,
hideNavigationBar: false,
autoCapitalize: 'sentences',
placeholder: 'Some text',
onChangeText: (e: NativeSyntheticEvent<{text: string}>) =>
setSearch(e.nativeEvent.text),
onCancelButtonPress: () => console.warn('Cancel button pressed'),
onSearchButtonPress: () => console.warn('Search button pressed'),
onFocus: () => console.warn('onFocus event'),
onBlur: () => console.warn('onBlur event'),
};

const items = [
'Apples',
'Pie',
'Juice',
'Cake',
'Nuggets',
'Some',
'Other',
'Stuff',
'To',
'Fill',
'The',
'Scrolling',
'Space',
];

return (
<ScrollView
contentInsetAdjustmentBehavior="automatic"
keyboardDismissMode="on-drag">
<Button
title="Tap me for second screen"
onPress={() => navigation.navigate('Second')}
/>
<Button
title="Tap me for ref"
onPress={() => (searchBarRef.current as any).focus()}
/>
{items
.filter(
(item) => item.toLowerCase().indexOf(search.toLowerCase()) !== -1,
)
.map((item) => (
<Button
title={item}
key={item}
onPress={() => {
console.warn(`${item} clicked`);
}}
/>
))}
</ScrollView>
);
}

function Second({navigation}: {navigation: NavigationProp<ParamListBase>}) {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Button
title="Tap me for first screen"
onPress={() => navigation.navigate('First')}
/>
</ScrollView>
);
}
45 changes: 45 additions & 0 deletions ios/RNSSearchBar.mm
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,49 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(onFocus, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onBlur, RCTBubblingEventBlock)

RCT_EXPORT_METHOD(focus : (NSNumber *_Nonnull)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
RNSSearchBar *searchBar = viewRegistry[reactTag];
UISearchController *searchBarController = searchBar.controller;
[searchBarController.searchBar becomeFirstResponder];
}];
}

RCT_EXPORT_METHOD(unfocus : (NSNumber *_Nonnull)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
RNSSearchBar *searchBar = viewRegistry[reactTag];
UISearchController *searchBarController = searchBar.controller;
[searchBarController.searchBar resignFirstResponder];
}];
}

RCT_EXPORT_METHOD(clearText : (NSNumber *_Nonnull)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
RNSSearchBar *searchBar = viewRegistry[reactTag];
UISearchController *searchBarController = searchBar.controller;
[searchBarController.searchBar setText:@""];
}];
}

RCT_EXPORT_METHOD(blur : (NSNumber *_Nonnull)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
RNSSearchBar *searchBar = viewRegistry[reactTag];
UISearchController *searchBarController = searchBar.controller;
[searchBarController.searchBar endEditing:true];
}];
}

RCT_EXPORT_METHOD(toggleCancelButton : (NSNumber *_Nonnull)reactTag flag : (BOOL *)flag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
RNSSearchBar *searchBar = viewRegistry[reactTag];
UISearchController *searchBarController = searchBar.controller;
[searchBarController.searchBar setShowsCancelButton:flag ? YES : NO animated:YES];
}];
}

@end
57 changes: 46 additions & 11 deletions src/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import {
Animated,
findNodeHandle,
NativeModules,
Image,
ImageProps,
Platform,
Expand Down Expand Up @@ -88,7 +90,13 @@ let NativeScreenStackHeaderSubview: React.ComponentType<React.PropsWithChildren<
ViewProps & { type?: HeaderSubviewTypes }
>>;
let AnimatedNativeScreen: React.ComponentType<ScreenProps>;
let NativeSearchBar: React.ComponentType<SearchBarProps>;
let NativeSearchBar: React.ComponentType<SearchBarProps> & {
unfocus: (reactTag: number | null) => void;
focus: (reactTag: number | null) => void;
blur: (reactTag: number | null) => void;
clearText: (reactTag: number | null) => void;
toggleCancelButton: (reactTag: number | null, flag: boolean) => void;
};
let NativeFullWindowOverlay: React.ComponentType<View>;

const ScreensNativeModules = {
Expand Down Expand Up @@ -364,6 +372,42 @@ const ScreenStackHeaderBackButtonImage = (props: ImageProps): JSX.Element => (
</ScreensNativeModules.NativeScreenStackHeaderSubview>
);

class SearchBar extends React.Component<SearchBarProps> {
blur() {
return NativeModules.RNSSearchBarManager.blur(findNodeHandle(this));
}

unfocus() {
return NativeModules.RNSSearchBarManager.unfocus(findNodeHandle(this));
}

focus() {
return NativeModules.RNSSearchBarManager.focus(findNodeHandle(this));
}

toggleCancelButton(flag: boolean) {
return NativeModules.RNSSearchBarManager.toggleCancelButton(
findNodeHandle(this),
flag
);
}

clearText() {
return NativeModules.RNSSearchBarManager.clearText(findNodeHandle(this));
}

render() {
if (!isSearchBarAvailableForCurrentPlatform) {
console.warn(
'Importing SearchBar is only valid on iOS and Android devices.'
);
return View;
}

return <ScreensNativeModules.NativeSearchBar {...this.props} />;
}
}

const ScreenStackHeaderRightView = (
props: React.PropsWithChildren<ViewProps>
): JSX.Element => (
Expand Down Expand Up @@ -429,6 +473,7 @@ module.exports = {
ScreenContainer,
ScreenContext,
ScreenStack,
SearchBar,

get NativeScreen() {
return ScreensNativeModules.NativeScreen;
Expand All @@ -448,16 +493,6 @@ module.exports = {
get ScreenStackHeaderSubview() {
return ScreensNativeModules.NativeScreenStackHeaderSubview;
},
get SearchBar() {
if (!isSearchBarAvailableForCurrentPlatform) {
console.warn(
'Importing SearchBar is only valid on iOS and Android devices.'
);
return View;
}

return ScreensNativeModules.NativeSearchBar;
},
get FullWindowOverlay() {
if (Platform.OS !== 'ios') {
console.warn('Importing FullWindowOverlay is only valid on iOS devices.');
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const ScreenStackHeaderCenterView = (
): JSX.Element => <View {...props} />;

export const ScreenStackHeaderSearchBarView = (
props: React.PropsWithChildren<SearchBarProps>
props: React.PropsWithChildren<Omit<SearchBarProps, 'ref'>>
): JSX.Element => <View {...props} />;

export const ScreenStackHeaderConfig: React.ComponentType<ScreenStackHeaderConfigProps> = View;
Expand Down
11 changes: 11 additions & 0 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import {
TextInputFocusEventData,
} from 'react-native';

type SearchBarRefObject = React.RefObject<{
focus: () => void;
blur: () => void;
clearText: () => void;
toggleCancelButton: (flag: boolean) => void;
}>;

export type SearchBarRef = NonNullable<SearchBarRefObject['current']>;

export type StackPresentationTypes =
| 'push'
| 'modal'
Expand Down Expand Up @@ -430,6 +439,8 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
}

export interface SearchBarProps {
ref?: React.RefObject<SearchBarRef>;

/**
* The auto-capitalization behavior
*/
Expand Down

0 comments on commit d553167

Please sign in to comment.