Skip to content

Commit dc18db0

Browse files
committed
initial commit
1 parent 43a213c commit dc18db0

File tree

10 files changed

+144
-38
lines changed

10 files changed

+144
-38
lines changed

ios/RNNComponentPresenter.mm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ - (void)applyOptions:(RNNNavigationOptions *)options {
8181
tintColor:[options.topBar.searchBar.tintColor
8282
withDefault:nil]
8383
cancelText:[withDefault.topBar.searchBar.cancelText
84-
withDefault:nil]];
84+
withDefault:nil]
85+
placement:[withDefault.topBar.searchBar.placement
86+
withDefault:SearchBarPlacementStacked]];
8587
}
8688

8789
[_topBarTitlePresenter applyOptions:withDefault.topBar];
@@ -145,7 +147,9 @@ - (void)mergeOptions:(RNNNavigationOptions *)mergeOptions
145147
tintColor:[mergeOptions.topBar.searchBar.tintColor
146148
withDefault:nil]
147149
cancelText:[withDefault.topBar.searchBar.cancelText
148-
withDefault:nil]];
150+
withDefault:nil]
151+
placement:[withDefault.topBar.searchBar.placement
152+
withDefault:SearchBarPlacementStacked]];
149153
} else {
150154
[viewController setSearchBarVisible:NO];
151155
}

ios/RNNSearchBarOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "RNNOptions.h"
2+
#import "RNNSearchBarPlacement.h"
23

34
@interface RNNSearchBarOptions : RNNOptions
45

@@ -11,5 +12,6 @@
1112
@property(nonatomic, strong) Color *tintColor;
1213
@property(nonatomic, strong) Text *placeholder;
1314
@property(nonatomic, strong) Text *cancelText;
15+
@property(nonatomic, strong) RNNSearchBarPlacement *placement;
1416

1517
@end

ios/RNNSearchBarOptions.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ - (instancetype)initWithDict:(NSDictionary *)dict {
1414
self.tintColor = [ColorParser parse:dict key:@"tintColor"];
1515
self.placeholder = [TextParser parse:dict key:@"placeholder"];
1616
self.cancelText = [TextParser parse:dict key:@"cancelText"];
17+
self.placement = (RNNSearchBarPlacement *)[EnumParser parse:dict
18+
key:@"placement"
19+
ofClass:RNNSearchBarPlacement.class];
1720
return self;
1821
}
1922

@@ -36,6 +39,8 @@ - (void)mergeOptions:(RNNSearchBarOptions *)options {
3639
self.placeholder = options.placeholder;
3740
if (options.cancelText.hasValue)
3841
self.cancelText = options.cancelText;
42+
if (options.placement.hasValue)
43+
self.placement = options.placement;
3944
}
4045

4146
@end

ios/RNNSearchBarPlacement.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#import "Enum.h"
2+
3+
typedef NS_ENUM(NSInteger, SearchBarPlacement) {
4+
SearchBarPlacementStacked = 0,
5+
SearchBarPlacementIntegrated
6+
};
7+
8+
@interface RNNSearchBarPlacement: Enum
9+
10+
- (SearchBarPlacement)get;
11+
12+
- (SearchBarPlacement)withDefault:(SearchBarPlacement)defaultValue;
13+
14+
@end

ios/RNNSearchBarPlacement.mm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#import "RNNSearchBarPlacement.h"
2+
#import <React/RCTConvert.h>
3+
4+
@implementation RNNSearchBarPlacement
5+
6+
- (SearchBarPlacement)convertString:(NSString *)string {
7+
return [self.class SearchBarPlacement:string];
8+
}
9+
10+
RCT_ENUM_CONVERTER(SearchBarPlacement, (@{
11+
@"stacked" : @(SearchBarPlacementStacked),
12+
@"integrated" : @(SearchBarPlacementIntegrated)
13+
}),
14+
SearchBarPlacementStacked, integerValue)
15+
16+
@end
17+

ios/UIViewController+RNNOptions.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#import "RNNSearchBarPlacement.h"
12
#import <UIKit/UIKit.h>
23

34
@class RNNBottomTabOptions;
@@ -15,7 +16,8 @@
1516
obscuresBackgroundDuringPresentation:(BOOL)obscuresBackgroundDuringPresentation
1617
backgroundColor:(nullable UIColor *)backgroundColor
1718
tintColor:(nullable UIColor *)tintColor
18-
cancelText:(NSString *_Nullable)cancelText;
19+
cancelText:(NSString *_Nullable)cancelText
20+
placement:(SearchBarPlacement)placement;
1921

2022
- (void)setSearchBarHiddenWhenScrolling:(BOOL)searchBarHidden;
2123

ios/UIViewController+RNNOptions.mm

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ - (void)setSearchBarWithOptions:(NSString *)placeholder
3030
obscuresBackgroundDuringPresentation:(BOOL)obscuresBackgroundDuringPresentation
3131
backgroundColor:(nullable UIColor *)backgroundColor
3232
tintColor:(nullable UIColor *)tintColor
33-
cancelText:(NSString *)cancelText {
33+
cancelText:(NSString *)cancelText
34+
placement:(SearchBarPlacement)placement {
3435
if (!self.navigationItem.searchController) {
3536
UISearchController *search =
3637
[[UISearchController alloc] initWithSearchResultsController:nil];
@@ -52,11 +53,12 @@ - (void)setSearchBarWithOptions:(NSString *)placeholder
5253
search.searchBar.searchTextField.backgroundColor = backgroundColor;
5354
}
5455

55-
if (focus) {
56-
dispatch_async(dispatch_get_main_queue(), ^{
57-
self.navigationItem.searchController.active = true;
58-
[self.navigationItem.searchController.searchBar becomeFirstResponder];
59-
});
56+
if (@available(iOS 26.0, *)) {
57+
if (placement == SearchBarPlacementIntegrated) {
58+
self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementIntegrated;
59+
} else {
60+
self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
61+
}
6062
}
6163

6264
self.navigationItem.searchController = search;
@@ -65,6 +67,22 @@ - (void)setSearchBarWithOptions:(NSString *)placeholder
6567
// Fixes #3450, otherwise, UIKit will infer the presentation context to
6668
// be the root most view controller
6769
self.definesPresentationContext = YES;
70+
71+
if (focus) {
72+
dispatch_async(dispatch_get_main_queue(), ^{
73+
self.navigationItem.searchController.active = true;
74+
[self.navigationItem.searchController.searchBar becomeFirstResponder];
75+
});
76+
}
77+
} else {
78+
// Update placement on existing searchController (iOS 26+)
79+
if (@available(iOS 26.0, *)) {
80+
if (placement == SearchBarPlacementIntegrated) {
81+
self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementIntegrated;
82+
} else {
83+
self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
84+
}
85+
}
6886
}
6987
}
7088

playground/src/screens/SearchBar.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import testIDs from '../testIDs';
99
const { HIDE_TOP_BAR_BTN, SHOW_TOP_BAR_BTN, SHOW_SEARCH_BAR_BTN, HIDE_SEARCH_BAR_BTN, TOP_BAR } =
1010
testIDs;
1111

12-
interface Props extends NavigationProps {}
12+
interface Props extends NavigationProps { }
1313

1414
export default class SearchBar extends React.Component<Props> {
1515
static options() {
@@ -26,6 +26,7 @@ export default class SearchBar extends React.Component<Props> {
2626

2727
state = {
2828
isAndroidNavigationBarVisible: true,
29+
placement: 'stacked' as 'stacked' | 'integrated',
2930
};
3031

3132
render() {
@@ -35,6 +36,10 @@ export default class SearchBar extends React.Component<Props> {
3536
<Button label="Show TopBar" testID={SHOW_TOP_BAR_BTN} onPress={this.showTopBar} />
3637
<Button label="Hide SearchBar" testID={HIDE_SEARCH_BAR_BTN} onPress={this.hideSearchBar} />
3738
<Button label="Show SearchBar" testID={SHOW_SEARCH_BAR_BTN} onPress={this.showSearchBar} />
39+
<Button
40+
label={`Toggle Placement (${this.state.placement})`}
41+
onPress={this.togglePlacement}
42+
/>
3843
</Root>
3944
);
4045
}
@@ -67,7 +72,21 @@ export default class SearchBar extends React.Component<Props> {
6772
topBar: {
6873
searchBar: {
6974
visible: true,
75+
placement: this.state.placement,
7076
},
7177
},
7278
});
79+
80+
togglePlacement = () => {
81+
const newPlacement = this.state.placement === 'stacked' ? 'integrated' : 'stacked';
82+
this.setState({ placement: newPlacement });
83+
Navigation.mergeOptions(this, {
84+
topBar: {
85+
searchBar: {
86+
visible: true,
87+
placement: newPlacement,
88+
},
89+
},
90+
});
91+
};
7392
}

playground/src/screens/SearchBarModal.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default class SearchBarModal extends React.Component<Props> {
2424

2525
state = {
2626
isAndroidNavigationBarVisible: true,
27+
placement: 'stacked' as 'stacked' | 'integrated',
2728
};
2829

2930
render() {
@@ -33,6 +34,10 @@ export default class SearchBarModal extends React.Component<Props> {
3334
{/* <Button label="Show TopBar" testID={SHOW_TOP_BAR_BTN} onPress={this.showTopBar} /> */}
3435
<Button label="Hide SearchBar" testID={HIDE_SEARCH_BAR_BTN} onPress={this.hideSearchBar} />
3536
<Button label="Show SearchBar" testID={SHOW_SEARCH_BAR_BTN} onPress={this.showSearchBar} />
37+
<Button
38+
label={`Toggle Placement (${this.state.placement})`}
39+
onPress={this.togglePlacement}
40+
/>
3641
</Root>
3742
);
3843
}
@@ -51,7 +56,21 @@ export default class SearchBarModal extends React.Component<Props> {
5156
topBar: {
5257
searchBar: {
5358
visible: true,
59+
placement: this.state.placement,
5460
},
5561
},
5662
});
63+
64+
togglePlacement = () => {
65+
const newPlacement = this.state.placement === 'stacked' ? 'integrated' : 'stacked';
66+
this.setState({ placement: newPlacement });
67+
Navigation.mergeOptions(this, {
68+
topBar: {
69+
searchBar: {
70+
visible: true,
71+
placement: newPlacement,
72+
},
73+
},
74+
});
75+
};
5776
}

src/interfaces/Options.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ export type Interpolation =
7676
| { type: 'linear' }
7777
| { type: 'overshoot'; tension?: number }
7878
| {
79-
type: 'spring';
80-
mass?: number;
81-
damping?: number;
82-
stiffness?: number;
83-
allowsOverdamping?: boolean;
84-
initialVelocity?: number;
85-
};
79+
type: 'spring';
80+
mass?: number;
81+
damping?: number;
82+
stiffness?: number;
83+
allowsOverdamping?: boolean;
84+
initialVelocity?: number;
85+
};
8686
interface ThemeColor {
8787
light?: string | symbol;
8888
dark?: string | symbol;
@@ -619,6 +619,12 @@ export interface OptionsSearchBar {
619619
tintColor?: Color;
620620
placeholder?: string;
621621
cancelText?: string;
622+
/**
623+
* iOS 26+ only. Controls where the search bar is placed.
624+
* - 'stacked': Below the navigation bar title (default, legacy behavior)
625+
* - 'integrated': Inside the navigation bar
626+
*/
627+
placement?: 'stacked' | 'integrated';
622628
}
623629

624630
export interface OptionsTopBar {
@@ -1124,7 +1130,7 @@ export interface SideMenuSide {
11241130
* #### (iOS specific)
11251131
* @default 'pushContent'
11261132
*/
1127-
openMode?: 'pushContent'|'aboveContent';
1133+
openMode?: 'pushContent' | 'aboveContent';
11281134
}
11291135

11301136
export interface OptionsSideMenu {
@@ -1400,39 +1406,39 @@ export interface StackAnimationOptions {
14001406
* Configure animations for the top bar
14011407
*/
14021408
topBar?:
1403-
| TopBarAnimationOptions
1404-
| {
1405-
enter?: TopBarAnimationOptions;
1406-
exit?: TopBarAnimationOptions;
1407-
};
1409+
| TopBarAnimationOptions
1410+
| {
1411+
enter?: TopBarAnimationOptions;
1412+
exit?: TopBarAnimationOptions;
1413+
};
14081414
/**
14091415
* Configure animations for the status bar (typically aligned
14101416
* with the top-bar's)
14111417
*/
14121418
statusBar?:
1413-
| StatusBarAnimationOptions
1414-
| {
1415-
enter?: StatusBarAnimationOptions;
1416-
exit?: StatusBarAnimationOptions;
1417-
};
1419+
| StatusBarAnimationOptions
1420+
| {
1421+
enter?: StatusBarAnimationOptions;
1422+
exit?: StatusBarAnimationOptions;
1423+
};
14181424
/**
14191425
* Configure animations for the bottom tabs
14201426
*/
14211427
bottomTabs?:
1422-
| ViewAnimationOptions
1423-
| {
1424-
enter?: ViewAnimationOptions;
1425-
exit?: ViewAnimationOptions;
1426-
};
1428+
| ViewAnimationOptions
1429+
| {
1430+
enter?: ViewAnimationOptions;
1431+
exit?: ViewAnimationOptions;
1432+
};
14271433
/**
14281434
* Configure animations for the content (Screen)
14291435
*/
14301436
content?:
1431-
| ViewAnimationOptions
1432-
| {
1433-
enter?: ViewAnimationOptions;
1434-
exit?: ViewAnimationOptions;
1435-
};
1437+
| ViewAnimationOptions
1438+
| {
1439+
enter?: ViewAnimationOptions;
1440+
exit?: ViewAnimationOptions;
1441+
};
14361442
/**
14371443
* Animations to be applied on elements which are shared between the appearing and disappearing screens
14381444
*/

0 commit comments

Comments
 (0)