@onekeyfe/react-native-bottom-tabs 0.0.0-stage → 6.7.0-11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +46 -0
- package/README.md +94 -2
- package/android/build.gradle +119 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomBarBlurRenderer.kt +92 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsPackage.kt +16 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsView.kt +1558 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsViewManager.kt +220 -0
- package/android/src/main/java/com/onekey/bottomtabs/FloatingTabBarStyle.kt +50 -0
- package/android/src/main/java/com/onekey/bottomtabs/ImageSource.kt +86 -0
- package/android/src/main/java/com/onekey/bottomtabs/TabInfo.kt +13 -0
- package/android/src/main/java/com/onekey/bottomtabs/Utils.kt +31 -0
- package/android/src/main/java/com/onekey/bottomtabs/events/OnNativeLayoutEvent.kt +20 -0
- package/android/src/main/java/com/onekey/bottomtabs/events/OnTabBarMeasuredEvent.kt +19 -0
- package/android/src/main/java/com/onekey/bottomtabs/events/PageSelectedEvent.kt +21 -0
- package/android/src/main/java/com/onekey/bottomtabs/events/TabDoublePressEvent.kt +19 -0
- package/common/cpp/react/renderer/components/RNBottomTabs/RNBottomTabsViewComponentDescriptor.h +32 -0
- package/common/cpp/react/renderer/components/RNBottomTabs/RNBottomTabsViewShadowNode.cpp +18 -0
- package/common/cpp/react/renderer/components/RNBottomTabs/RNBottomTabsViewShadowNode.h +35 -0
- package/common/cpp/react/renderer/components/RNBottomTabs/RNBottomTabsViewState.cpp +15 -0
- package/common/cpp/react/renderer/components/RNBottomTabs/RNBottomTabsViewState.h +25 -0
- package/docs/SPEC.md +168 -0
- package/ios/Extensions.swift +46 -0
- package/ios/RCTBottomAccessoryComponentView.h +12 -0
- package/ios/RCTBottomAccessoryComponentView.mm +67 -0
- package/ios/RCTBottomAccessoryContainerView.swift +51 -0
- package/ios/RCTBottomTabsComponentView.h +12 -0
- package/ios/RCTBottomTabsComponentView.mm +338 -0
- package/ios/RCTBottomTabsContainerView.swift +777 -0
- package/ios/RCTBottomTabsLog.h +7 -0
- package/ios/RCTBottomTabsLog.m +32 -0
- package/ios/SVG/CoreSVG.h +13 -0
- package/ios/SVG/CoreSVG.mm +177 -0
- package/ios/SVG/SvgDecoder.h +10 -0
- package/ios/SVG/SvgDecoder.mm +32 -0
- package/ios/TabBarFontSize.swift +55 -0
- package/lib/module/BottomAccessoryView.js +45 -0
- package/lib/module/BottomAccessoryViewNativeComponent.ts +27 -0
- package/lib/module/BottomTabs.js +325 -0
- package/lib/module/BottomTabsNativeComponent.ts +70 -0
- package/lib/module/DelayedFreeze.js +31 -0
- package/lib/module/NativeSVGDecoder.js +5 -0
- package/lib/module/SceneMap.js +28 -0
- package/lib/module/codegen-types.d.js +2 -0
- package/lib/module/index.js +20 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/utils/BottomTabBarHeightContext.js +5 -0
- package/lib/module/utils/useBottomTabBarHeight.js +12 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/BottomAccessoryView.d.ts +8 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts +16 -0
- package/lib/typescript/src/BottomTabs.d.ts +209 -0
- package/lib/typescript/src/BottomTabsNativeComponent.d.ts +59 -0
- package/lib/typescript/src/DelayedFreeze.d.ts +9 -0
- package/lib/typescript/src/NativeSVGDecoder.d.ts +6 -0
- package/lib/typescript/src/SceneMap.d.ts +10 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/types.d.ts +29 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts +3 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts +2 -0
- package/package.json +117 -4
- package/react-native-bottom-tabs.podspec +36 -0
- package/react-native.config.js +13 -0
- package/src/BottomAccessoryView.tsx +58 -0
- package/src/BottomAccessoryViewNativeComponent.ts +27 -0
- package/src/BottomTabs.tsx +559 -0
- package/src/BottomTabsNativeComponent.ts +70 -0
- package/src/DelayedFreeze.tsx +33 -0
- package/src/NativeSVGDecoder.ts +5 -0
- package/src/SceneMap.tsx +34 -0
- package/src/codegen-types.d.ts +28 -0
- package/src/index.tsx +18 -0
- package/src/types.ts +31 -0
- package/src/utils/BottomTabBarHeightContext.ts +5 -0
- package/src/utils/useBottomTabBarHeight.ts +15 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Native Tab View behavioral contract
|
|
2
|
+
|
|
3
|
+
## Tab presses and double press
|
|
4
|
+
|
|
5
|
+
Status: implemented in module source. Runtime verification status is recorded
|
|
6
|
+
under Conformance and acceptance.
|
|
7
|
+
|
|
8
|
+
Every tab press that the native tab bar accepts emits `onPageSelected` with the
|
|
9
|
+
tab key, including a press on the already selected tab (reselection). A
|
|
10
|
+
reselection never changes the selected page. A tab with `preventsDefault` emits
|
|
11
|
+
`onPageSelected` but keeps the current page selected.
|
|
12
|
+
|
|
13
|
+
`onTabDoublePress` fires when two consecutive accepted presses target the same
|
|
14
|
+
tab within the platform double-tap timeout (iOS 0.3 s; Android
|
|
15
|
+
`ViewConfiguration.getDoubleTapTimeout()`, 300 ms by default). Both presses still
|
|
16
|
+
emit `onPageSelected` first; the double press is an additional event, emitted
|
|
17
|
+
after the second press. After it fires, tracking resets, so a third quick press
|
|
18
|
+
starts a new pair. Double press is derived from accepted selections only: no
|
|
19
|
+
extra gesture recognizer is attached and single-press latency is unchanged.
|
|
20
|
+
JS-driven `selectedPage` changes do not count as presses.
|
|
21
|
+
|
|
22
|
+
Tab long press is not supported. The former `onTabLongPress` prop/event and the
|
|
23
|
+
native long-press recognizers were removed.
|
|
24
|
+
|
|
25
|
+
## iOS native tab bar gestures
|
|
26
|
+
|
|
27
|
+
The iOS tab bar keeps UIKit's own gesture handling. On iOS 26 this includes the
|
|
28
|
+
Liquid Glass press-and-drag selection: pressing a tab lifts the lens and dragging
|
|
29
|
+
scrubs across tabs, selecting on release through the normal
|
|
30
|
+
`tabBarController(_:shouldSelect:)` path. The module MUST NOT install recognizers
|
|
31
|
+
that delay, cancel, or require failure of the system tab bar gestures.
|
|
32
|
+
|
|
33
|
+
The previous drag-cancellation guard (which refused selection for any touch that
|
|
34
|
+
moved more than 10 points) was removed because it disabled drag-to-switch.
|
|
35
|
+
|
|
36
|
+
## Android tab bar style
|
|
37
|
+
|
|
38
|
+
`androidTabBarStyle` selects the Android presentation; iOS ignores it.
|
|
39
|
+
|
|
40
|
+
- `default` (default): Material `BottomNavigationView` docked at the bottom,
|
|
41
|
+
with the blur-controlled scene underlap described below.
|
|
42
|
+
- `floating`: a capsule bar modeled on the iOS 26 tab bar. Scene content extends
|
|
43
|
+
underneath it.
|
|
44
|
+
|
|
45
|
+
Floating geometry (dp): bar height 62, side margin 20 plus the navigation-bar
|
|
46
|
+
side insets, bottom gap `max(bottomInset, 20) + 4`, fully rounded corners. The
|
|
47
|
+
bottom inset is the navigation-bar inset (0 when `ignoreBottomInsets`) or the
|
|
48
|
+
keyboard inset when `ignoreKeyboardInsets` is false, whichever is larger. Icons
|
|
49
|
+
are 26, labels 10 sp in both states unless `fontSize` is set, and item content is
|
|
50
|
+
vertically centered. The reported tab bar height is the bar height plus its
|
|
51
|
+
bottom gap, or 0 while hidden.
|
|
52
|
+
|
|
53
|
+
Floating appearance with background blur disabled: the bar surface is `barTintColor` (or the theme background)
|
|
54
|
+
at 95% opacity (lightened slightly for dark colors), with
|
|
55
|
+
a hairline rim and an outline shadow. When background blur is enabled (see below), the bottom strip extends from
|
|
56
|
+
24 dp above the capsule to the bottom of the scene, including the gap beneath
|
|
57
|
+
the capsule. The existing capsule keeps its rim/shadow and uses a 12 percent
|
|
58
|
+
surface fill over the blurred strip; its lens and navigation controls remain
|
|
59
|
+
sharp. The selected tab sits on a capsule pill inset 4 dp
|
|
60
|
+
(`activeIndicatorColor` when provided). The Material ripple is disabled; the
|
|
61
|
+
lens provides press feedback.
|
|
62
|
+
|
|
63
|
+
Floating interaction (single finger; motion splitting is disabled):
|
|
64
|
+
|
|
65
|
+
1. Touch down on any tab moves the pill to that tab and lifts it into a lens
|
|
66
|
+
(grows past the bar edges, gains a rim) and magnifies that tab's content.
|
|
67
|
+
2. Movement beyond touch slop starts a drag: the lens follows the finger with
|
|
68
|
+
edge resistance and stretches with velocity; the tab nearest the lens center
|
|
69
|
+
becomes the candidate, is magnified, and ticks haptically when it changes.
|
|
70
|
+
No page change or event happens during the drag.
|
|
71
|
+
3. Release without drag performs the tab's click (selection or reselection).
|
|
72
|
+
Release displacement is also checked against touch slop, including when no
|
|
73
|
+
MOVE was delivered or the last MOVE remained within slop. If release crosses
|
|
74
|
+
slop, it starts a drag and updates the candidate from the release position.
|
|
75
|
+
Release after a drag selects the candidate once, only if it differs from the
|
|
76
|
+
current tab. Cancel selects nothing.
|
|
77
|
+
4. The lens then settles back into the pill on the selected tab, which stays on
|
|
78
|
+
the old tab for cancelled or `preventsDefault` presses.
|
|
79
|
+
|
|
80
|
+
JS-driven selection changes slide the pill with a spring and velocity stretch.
|
|
81
|
+
Switching style at runtime rebuilds the bar and restores Material's label sizes,
|
|
82
|
+
icon size, ripple, padding, and gravity when returning to `default`.
|
|
83
|
+
|
|
84
|
+
## Android bottom background blur
|
|
85
|
+
|
|
86
|
+
Status: implemented in module source. The renamed package passed TypeScript
|
|
87
|
+
checking, Android compilation with regenerated Fabric bindings, eight React
|
|
88
|
+
wrapper tests, and ten direct-native API 36 tests (five each for docked and
|
|
89
|
+
floating styles). Style switching with blur active received focused regression
|
|
90
|
+
coverage. Complete consumer-app and physical-device acceptance remain pending;
|
|
91
|
+
API 31-35 and visual dark-mode acceptance have not been reverified for this port.
|
|
92
|
+
|
|
93
|
+
`androidBottomBlurEnabled?: boolean` controls the native Android tab bar's live
|
|
94
|
+
background blur, translucent surface tint, and 24 dp fade into the page above it.
|
|
95
|
+
Omitting it (including `undefined`) MUST enable the effect only on Android 16
|
|
96
|
+
(API 36) and newer. `false` MUST restore the existing appearance and layout for the current
|
|
97
|
+
`androidTabBarStyle`: docked scenes reserve bar height; floating scenes retain
|
|
98
|
+
their existing underlap and lens gestures. `true` MAY enable it on Android 12 (API 31) and newer; older
|
|
99
|
+
Android versions and software-rendered windows MUST retain the existing layout
|
|
100
|
+
and appearance. iOS and custom JS tab bars MUST ignore this option.
|
|
101
|
+
|
|
102
|
+
When active, the selected scene MUST fill the native container behind the tab
|
|
103
|
+
bar; icons, labels, badges, and the style's press feedback MUST stay sharp and interactive.
|
|
104
|
+
The reported scene dimensions MUST include this underlap, while tab-bar height
|
|
105
|
+
MUST retain its existing meaning (including system/IME padding unless ignored).
|
|
106
|
+
Callers MUST use `useBottomTabBarHeight` to pad scrollable content when its final
|
|
107
|
+
row must scroll clear of the bar. Hiding the bar MUST remove the effect and
|
|
108
|
+
report zero bar height. Toggling, resizing, theme changes, and system/keyboard
|
|
109
|
+
insets MUST refresh geometry and appearance without remounting scenes.
|
|
110
|
+
|
|
111
|
+
The module owns the GPU resources on the UI thread. It MUST sample only the
|
|
112
|
+
scene layer, excluding the tab bar and its own output. It MUST use hardware
|
|
113
|
+
RenderNode/RenderEffect rendering of a bottom strip at one-quarter resolution
|
|
114
|
+
in each dimension, with a 20 dp blur radius and 40 dp sampling margin above the
|
|
115
|
+
fade. It MUST NOT capture bitmaps, read pixels back to the CPU, emit scroll
|
|
116
|
+
events to JS, or run a recurring frame callback for this effect. Rendering MUST
|
|
117
|
+
follow the normal view invalidation cycle, using the scene's existing display
|
|
118
|
+
lists. The translucent tint MUST follow `tabBarStyle.backgroundColor` when set,
|
|
119
|
+
otherwise the docked Material surface color or floating theme background,
|
|
120
|
+
with opacity capped at 78 percent.
|
|
121
|
+
|
|
122
|
+
Only one blur RenderNode and cached paints/shaders MAY be retained per mounted
|
|
123
|
+
tab view. Its offscreen dimensions MUST be bounded by one-quarter of the view
|
|
124
|
+
width and the smaller of the view height or reported bar footprint plus 64 dp (rounded up).
|
|
125
|
+
Disabling, hiding, detaching, and dropping the native view MUST discard the
|
|
126
|
+
display list; no scene snapshots, disk cache, timers, or background workers are
|
|
127
|
+
owned. SurfaceView content is outside the normal View display list and is not
|
|
128
|
+
supported as a blur source. Liquid Glass refraction and shape morphing are out
|
|
129
|
+
of scope.
|
|
130
|
+
|
|
131
|
+
Acceptance MUST cover API 35/36 default behavior and explicit overrides, live
|
|
132
|
+
scrolling under the bar with sharp controls, on/off restoration, hidden bars,
|
|
133
|
+
tab changes, resize, dark/light themes, ignored bottom insets, and both keyboard
|
|
134
|
+
inset modes. A representative scroll comparison MUST distinguish UI/RenderThread
|
|
135
|
+
timings and missed frames from visual correctness. Source/type checks and
|
|
136
|
+
emulator measurements MUST NOT be reported as physical-device performance.
|
|
137
|
+
|
|
138
|
+
Implementation: `src/BottomTabs.tsx` owns the public default and custom-bar guard;
|
|
139
|
+
`src/BottomTabsNativeComponent.ts` and `BottomTabsViewManager.kt` define the Fabric
|
|
140
|
+
boundary; `BottomTabsView.kt` owns scene geometry, appearance, and disposal;
|
|
141
|
+
`BottomBarBlurRenderer.kt` owns the bounded GPU pipeline. Focused native tests
|
|
142
|
+
and runner instructions are in `native-tests/android`. The example's settings
|
|
143
|
+
page exposes the switch and its scrolling scene reserves the reported bar height.
|
|
144
|
+
|
|
145
|
+
## Threading and ownership
|
|
146
|
+
|
|
147
|
+
All gesture handling, animations, and timers run on the platform UI thread in
|
|
148
|
+
the main-runtime native view. The view owns no background-runtime or shared JS
|
|
149
|
+
objects. Retained state is bounded to the current touch sequence, the last
|
|
150
|
+
pressed tab and timestamp, and at most two running animators.
|
|
151
|
+
|
|
152
|
+
## Conformance and acceptance
|
|
153
|
+
|
|
154
|
+
Implementation:
|
|
155
|
+
- iOS: `ios/RCTBottomTabsContainerView.swift` (`shouldSelectTab`,
|
|
156
|
+
`emitDoublePressIfNeeded`), `ios/RCTBottomTabsComponentView.mm` event forwarding.
|
|
157
|
+
- Android: `android/src/main/java/com/onekey/bottomtabs/BottomTabsView.kt` (floating
|
|
158
|
+
layout, `handleFloatingTabTouch`, `emitDoublePressIfNeeded`),
|
|
159
|
+
`FloatingTabBarStyle.kt`, `events/TabDoublePressEvent.kt`.
|
|
160
|
+
|
|
161
|
+
Required runtime cases per platform: stationary tap selects; reselect emits
|
|
162
|
+
`onPageSelected` without page change; two quick taps on one tab emit
|
|
163
|
+
`onTabDoublePress` exactly once; slow taps do not. iOS 26: press-and-drag across
|
|
164
|
+
tabs selects the tab under the finger on release. Android floating: press on a
|
|
165
|
+
non-selected tab lifts the lens there; drag crossing tab centers does not change
|
|
166
|
+
pages; release selects the nearest tab once; cancel restores; hidden bar, dark
|
|
167
|
+
mode, and 3-button navigation insets lay out correctly. Source compilation is
|
|
168
|
+
recorded separately from runtime acceptance.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
extension Collection {
|
|
4
|
+
subscript(safe index: Index) -> Element? {
|
|
5
|
+
indices.contains(index) ? self[index] : nil
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
extension Collection where Element == TabItem {
|
|
10
|
+
func findByKey(_ key: String?) -> Element? {
|
|
11
|
+
guard let key else { return nil }
|
|
12
|
+
guard !isEmpty else { return nil }
|
|
13
|
+
return first { $0.key == key }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
extension UIView {
|
|
18
|
+
func pinEdges(to other: UIView) {
|
|
19
|
+
NSLayoutConstraint.activate([
|
|
20
|
+
leadingAnchor.constraint(equalTo: other.leadingAnchor),
|
|
21
|
+
trailingAnchor.constraint(equalTo: other.trailingAnchor),
|
|
22
|
+
topAnchor.constraint(equalTo: other.topAnchor),
|
|
23
|
+
bottomAnchor.constraint(equalTo: other.bottomAnchor)
|
|
24
|
+
])
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
extension UIImage {
|
|
29
|
+
func resizeImageTo(size: CGSize) -> UIImage? {
|
|
30
|
+
let renderer = UIGraphicsImageRenderer(size: size)
|
|
31
|
+
return renderer.image { _ in
|
|
32
|
+
self.draw(in: CGRect(origin: .zero, size: size))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
extension UIColor {
|
|
38
|
+
convenience init(rgb: Int) {
|
|
39
|
+
self.init(
|
|
40
|
+
red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
|
|
41
|
+
green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
|
|
42
|
+
blue: CGFloat(rgb & 0xFF) / 255.0,
|
|
43
|
+
alpha: CGFloat((rgb >> 24) & 0xFF) / 255.0
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
2
|
+
#import <React/RCTViewComponentView.h>
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
@interface RCTBottomAccessoryComponentView: RCTViewComponentView
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
NS_ASSUME_NONNULL_END
|
|
11
|
+
|
|
12
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#import "RCTBottomAccessoryComponentView.h"
|
|
2
|
+
|
|
3
|
+
#import <react/renderer/components/RNBottomTabs/ComponentDescriptors.h>
|
|
4
|
+
#import <react/renderer/components/RNBottomTabs/EventEmitters.h>
|
|
5
|
+
#import <react/renderer/components/RNBottomTabs/Props.h>
|
|
6
|
+
#import <react/renderer/components/RNBottomTabs/RCTComponentViewHelpers.h>
|
|
7
|
+
|
|
8
|
+
#import <React/RCTFabricComponentsPlugins.h>
|
|
9
|
+
|
|
10
|
+
#if __has_include(<BottomTabsModule/BottomTabsModule-Swift.h>)
|
|
11
|
+
#import <BottomTabsModule/BottomTabsModule-Swift.h>
|
|
12
|
+
#elif __has_include("BottomTabsModule/BottomTabsModule-Swift.h")
|
|
13
|
+
#import "BottomTabsModule/BottomTabsModule-Swift.h"
|
|
14
|
+
#elif __has_include("BottomTabsModule-Swift.h")
|
|
15
|
+
#import "BottomTabsModule-Swift.h"
|
|
16
|
+
#else
|
|
17
|
+
#import "react_native_bottom_tabs-Swift.h"
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
using namespace facebook::react;
|
|
21
|
+
|
|
22
|
+
@implementation RCTBottomAccessoryComponentView {
|
|
23
|
+
UIView *_accessoryView;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
27
|
+
{
|
|
28
|
+
return concreteComponentDescriptorProvider<RNBottomTabsAccessoryViewComponentDescriptor>();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
32
|
+
{
|
|
33
|
+
if (self = [super initWithFrame:frame]) {
|
|
34
|
+
static const auto defaultProps = std::make_shared<const RNBottomTabsAccessoryViewProps>();
|
|
35
|
+
_props = defaultProps;
|
|
36
|
+
|
|
37
|
+
Class containerClass = NSClassFromString(@"BottomTabsModule.RCTBottomAccessoryContainerView");
|
|
38
|
+
if (!containerClass) {
|
|
39
|
+
containerClass = NSClassFromString(@"RCTBottomAccessoryContainerView");
|
|
40
|
+
}
|
|
41
|
+
_accessoryView = [[containerClass alloc] init];
|
|
42
|
+
self.contentView = _accessoryView;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return self;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)setFrame:(CGRect)frame
|
|
49
|
+
{
|
|
50
|
+
[super setFrame:frame];
|
|
51
|
+
if (frame.size.width > 0 && frame.size.height > 0) {
|
|
52
|
+
auto eventEmitter = std::static_pointer_cast<const RNBottomTabsAccessoryViewEventEmitter>(_eventEmitter);
|
|
53
|
+
if (eventEmitter) {
|
|
54
|
+
eventEmitter->onNativeLayout(RNBottomTabsAccessoryViewEventEmitter::OnNativeLayout {
|
|
55
|
+
.width = frame.size.width,
|
|
56
|
+
.height = frame.size.height
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@end
|
|
63
|
+
|
|
64
|
+
Class<RCTComponentViewProtocol> RNBottomTabsAccessoryViewCls(void)
|
|
65
|
+
{
|
|
66
|
+
return RCTBottomAccessoryComponentView.class;
|
|
67
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
extension Notification.Name {
|
|
6
|
+
static let bottomAccessoryPlacementChanged = Notification.Name("bottomAccessoryPlacementChanged")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class RCTBottomAccessoryContainerView: UIView {
|
|
10
|
+
|
|
11
|
+
// MARK: - Events
|
|
12
|
+
@objc var onNativeLayout: RCTDirectEventBlock?
|
|
13
|
+
@objc var onPlacementChanged: RCTDirectEventBlock?
|
|
14
|
+
|
|
15
|
+
// MARK: - Init
|
|
16
|
+
|
|
17
|
+
override init(frame: CGRect) {
|
|
18
|
+
super.init(frame: frame)
|
|
19
|
+
|
|
20
|
+
NotificationCenter.default.addObserver(
|
|
21
|
+
self,
|
|
22
|
+
selector: #selector(handlePlacementNotification(_:)),
|
|
23
|
+
name: .bottomAccessoryPlacementChanged,
|
|
24
|
+
object: nil
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
required init?(coder: NSCoder) {
|
|
29
|
+
fatalError("init(coder:) has not been implemented")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
deinit {
|
|
33
|
+
NotificationCenter.default.removeObserver(self)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: - Layout
|
|
37
|
+
|
|
38
|
+
override func layoutSubviews() {
|
|
39
|
+
super.layoutSubviews()
|
|
40
|
+
let bounds = self.bounds
|
|
41
|
+
guard bounds.width > 0 && bounds.height > 0 else { return }
|
|
42
|
+
onNativeLayout?(["width": Double(bounds.width), "height": Double(bounds.height)])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - Placement
|
|
46
|
+
|
|
47
|
+
@objc private func handlePlacementNotification(_ notification: Notification) {
|
|
48
|
+
guard let placement = notification.userInfo?["placement"] as? String else { return }
|
|
49
|
+
onPlacementChanged?(["placement": placement])
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
2
|
+
#import <React/RCTViewComponentView.h>
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
@interface RCTBottomTabsComponentView: RCTViewComponentView
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
NS_ASSUME_NONNULL_END
|
|
11
|
+
|
|
12
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|