diff --git a/.changeset/tidy-tools-smile.md b/.changeset/tidy-tools-smile.md new file mode 100644 index 00000000..def8f7c5 --- /dev/null +++ b/.changeset/tidy-tools-smile.md @@ -0,0 +1,5 @@ +--- +"react-native-bottom-tabs": major +--- + +Disable Apple-platform SVG tab icon support by default and add the `$RNBottomTabsEnableSVG` Podfile opt-in and the `RN_BOTTOM_TABS_ENABLE_SVG=1` Swift Package Manager opt-in. The optional CoreSVG decoder is now scoped to bottom-tab icons instead of being registered app-wide with React Native's image loader. diff --git a/apps/example/ios/Podfile b/apps/example/ios/Podfile index 09683801..5eff6406 100644 --- a/apps/example/ios/Podfile +++ b/apps/example/ios/Podfile @@ -1,4 +1,5 @@ ENV['RCT_NEW_ARCH_ENABLED'] = '1' +$RNBottomTabsEnableSVG = ENV['RN_BOTTOM_TABS_ENABLE_SVG'] == '1' ws_dir = Pathname.new(__dir__) ws_dir = ws_dir.parent until diff --git a/docs/docs/docs/guides/usage-with-react-navigation.mdx b/docs/docs/docs/guides/usage-with-react-navigation.mdx index 8ff246ac..891c20e2 100644 --- a/docs/docs/docs/guides/usage-with-react-navigation.mdx +++ b/docs/docs/docs/guides/usage-with-react-navigation.mdx @@ -272,8 +272,6 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon` component={Albums} options={{ tabBarIcon: () => require('person.png'), - // SVG is also supported - tabBarIcon: () => require('person.svg'), // or tabBarIcon: () => ({ sfSymbol: 'person' }), // You can also pass a URL @@ -286,6 +284,46 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon` SF Symbols are only supported on Apple platforms. ::: +#### SVG icons on Apple platforms + +SVG tab icons remain available by default on Android. On Apple platforms, SVG support is disabled by default because its native decoder uses private CoreSVG APIs. PNG, JPEG, and SF Symbol icons do not need this decoder. + +To opt in, set the flag before React Native configures pods in your `Podfile`, then run `pod install` and rebuild the native app: + +```ruby +$RNBottomTabsEnableSVG = true + +# require React Native's pod helpers and call use_react_native! below +``` + +With the decoder enabled, local and remote SVG sources can be used like other image sources: + +```tsx +tabBarIcon: () => require('person.svg') +``` + +If your app uses Swift Package Manager instead of CocoaPods, set the `RN_BOTTOM_TABS_ENABLE_SVG` environment variable to `1` when SwiftPM resolves packages. For example, run the sync and the build from the same shell: + +```sh +export RN_BOTTOM_TABS_ENABLE_SVG=1 +npx react-native spm +npx react-native run-ios +``` + +Xcode reads this variable only when it is set in Xcode's own environment. If you open the project from the Dock or Finder, the variable from your shell does not reach it. + +Enabling this flag compiles the optional CoreSVG implementation into the Apple-platform library. The decoder is scoped to bottom-tab icons and is not registered with React Native's app-wide image loader. + +If you are upgrading from a version where Apple-platform SVG support was enabled automatically, add this flag before upgrading to preserve your existing SVG tab icons. Expo prebuild projects can use the package's `enableSVG` plugin option instead: + +```json +{ + "expo": { + "plugins": [["react-native-bottom-tabs", { "enableSVG": true }]] + } +} +``` + #### `tabBarIconRenderingMode` Rendering mode for image icons. diff --git a/packages/example-shared/src/Examples/NativeBottomTabsEmbeddedStacks.tsx b/packages/example-shared/src/Examples/NativeBottomTabsEmbeddedStacks.tsx index 63fe64aa..a974ffb5 100644 --- a/packages/example-shared/src/Examples/NativeBottomTabsEmbeddedStacks.tsx +++ b/packages/example-shared/src/Examples/NativeBottomTabsEmbeddedStacks.tsx @@ -94,8 +94,7 @@ function NativeBottomTabsEmbeddedStacks() { name="Chat" component={ChatStackScreen} options={{ - tabBarIcon: () => - require('../../assets/icons/message-circle-code.svg'), + tabBarIcon: () => require('../../assets/icons/chat_dark.png'), }} /> diff --git a/packages/example-shared/src/Examples/TintColors.tsx b/packages/example-shared/src/Examples/TintColors.tsx index 62c87340..bce3cd0c 100644 --- a/packages/example-shared/src/Examples/TintColors.tsx +++ b/packages/example-shared/src/Examples/TintColors.tsx @@ -44,9 +44,7 @@ export default function TintColorsExample() { }, { key: 'chat', - focusedIcon: { - uri: 'https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg', - }, + focusedIcon: require('../../assets/icons/chat_dark.png'), title: 'Chat', }, ]); diff --git a/packages/react-native-bottom-tabs/Package.swift b/packages/react-native-bottom-tabs/Package.swift index 70720fff..57890686 100644 --- a/packages/react-native-bottom-tabs/Package.swift +++ b/packages/react-native-bottom-tabs/Package.swift @@ -2,12 +2,24 @@ import PackageDescription +// Apple-platform SVG tab icons use private CoreSVG APIs, so they are opt-in. +// Set RN_BOTTOM_TABS_ENABLE_SVG=1 when SwiftPM resolves the package to enable them. +let enableSVG = Context.environment["RN_BOTTOM_TABS_ENABLE_SVG"] == "1" + let reactHeaders: [Target.Dependency] = [ .product(name: "ReactHeaders", package: "ReactNative"), .product(name: "ReactNativeHeaders", package: "ReactNative"), .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), ] +let svgTargets: [Target] = enableSVG ? [ + .target( + name: "BottomTabsSVG", + path: "ios/SVG", + publicHeadersPath: "." + ), +] : [] + let package = Package( name: "react_native_bottom_tabs", platforms: [.iOS(.v15)], @@ -30,7 +42,7 @@ let package = Package( name: "BottomTabsSwift", dependencies: reactHeaders + [ .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), - ], + ] + (enableSVG ? ["BottomTabsSVG"] : []), path: "ios", exclude: [ "RCTTabViewComponentView.h", @@ -41,7 +53,8 @@ let package = Package( "SVG", "include", "Bridge", - ] + ], + swiftSettings: enableSVG ? [.define("RN_BOTTOM_TABS_ENABLE_SVG")] : [] ), .target( name: "BottomTabsBridge", @@ -60,7 +73,6 @@ let package = Package( sources: [ "ios/RCTTabViewComponentView.mm", "ios/RCTBottomAccessoryComponentView.mm", - "ios/SVG", "common/cpp", ], publicHeadersPath: "ios/include", @@ -72,7 +84,7 @@ let package = Package( .define("NDEBUG", .when(configuration: .release)), ] ), - ], + ] + svgTargets, swiftLanguageModes: [.v5], cxxLanguageStandard: .cxx20 ) diff --git a/packages/react-native-bottom-tabs/README.md b/packages/react-native-bottom-tabs/README.md index f4fc5785..fec123e6 100644 --- a/packages/react-native-bottom-tabs/README.md +++ b/packages/react-native-bottom-tabs/README.md @@ -19,6 +19,8 @@ The full documentation can be found on our [website](https://oss.callstack.com/react-native-bottom-tabs/). +SVG tab icons on Apple platforms are disabled by default. See the [icon configuration guide](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation#svg-icons-on-apple-platforms) for the Podfile and Expo opt-ins. + ## Contributing See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. diff --git a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h index 5e7fde1d..0ba14eea 100644 --- a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h +++ b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h @@ -1,10 +1,26 @@ -#ifdef __cplusplus +#import +#import + +#if TARGET_OS_OSX +#import +#else +#import +#endif -#import -#import +NS_ASSUME_NONNULL_BEGIN -@interface SvgDecoder : NSObject +#ifdef __cplusplus +extern "C" { +#endif -@end +#if TARGET_OS_OSX +NSImage *_Nullable RNBottomTabsDecodeSVGData(NSData *data); +#else +UIImage *_Nullable RNBottomTabsDecodeSVGData(NSData *data); +#endif +#ifdef __cplusplus +} #endif + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm index 1bbddd04..819bdf00 100644 --- a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm +++ b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm @@ -1,38 +1,11 @@ #import "SvgDecoder.h" #import "CoreSVG.h" -@implementation SvgDecoder - -RCT_EXPORT_MODULE() - -- (BOOL)canDecodeImageData:(NSData *)imageData -{ - return [CoreSVGWrapper isSVGData:imageData]; -} - -- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode - completionHandler:(RCTImageLoaderCompletionBlock)completionHandler +PlatformImage *RNBottomTabsDecodeSVGData(NSData *data) { - UIImage *image = [CoreSVGWrapper.shared imageFromSVGData:imageData]; - - if (image) { - completionHandler(nil, image); - } else { - NSError *error = [NSError errorWithDomain:@"SVGDecoderErrorDomain" - code:2 - userInfo:@{NSLocalizedDescriptionKey: @"Failed to render SVG to image"}]; - completionHandler(error, nil); + if (![CoreSVGWrapper isSVGData:data]) { + return nil; } - return ^{}; -} -- (std::shared_ptr)getTurboModule: -(const facebook::react::ObjCTurboModule::InitParams &)params -{ - return std::make_shared(params); + return [CoreSVGWrapper.shared imageFromSVGData:data]; } - -@end diff --git a/packages/react-native-bottom-tabs/ios/SVGIconLoader.swift b/packages/react-native-bottom-tabs/ios/SVGIconLoader.swift new file mode 100644 index 00000000..8d836e2b --- /dev/null +++ b/packages/react-native-bottom-tabs/ios/SVGIconLoader.swift @@ -0,0 +1,44 @@ +#if RN_BOTTOM_TABS_ENABLE_SVG + import Foundation + #if SWIFT_PACKAGE + import BottomTabsSVG + #endif + + /// Loads SVG tab icons with the opt-in CoreSVG decoder. + enum SVGIconLoader { + static func isSVGRequest(_ request: URLRequest) -> Bool { + guard let url = request.url else { return false } + return url.pathExtension.lowercased() == "svg" + || url.absoluteString.lowercased().hasPrefix("data:image/svg+xml") + } + + static func decode( + with request: URLRequest, + completion: @escaping (PlatformImage?) -> Void + ) { + guard let url = request.url else { + completion(nil) + return + } + + let decode: (Data?) -> Void = { data in + guard let data else { + completion(nil) + return + } + completion(RNBottomTabsDecodeSVGData(data)) + } + + if url.scheme == "http" || url.scheme == "https" { + let task = URLSession.shared.dataTask(with: request) { data, _, _ in + decode(data) + } + task.resume() + } else { + DispatchQueue.global(qos: .userInitiated).async { + decode(try? Data(contentsOf: url)) + } + } + } + } +#endif diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift index e5663453..bfb7f73c 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift @@ -264,6 +264,19 @@ public final class TabInfo: NSObject { if let imageSources = icons as? [RCTImageSource?] { for (index, imageSource) in imageSources.enumerated() { guard let imageSource else { continue } + + #if RN_BOTTOM_TABS_ENABLE_SVG + if SVGIconLoader.isSVGRequest(imageSource.request) { + loadSVGIcon( + with: imageSource.request, + at: index, + focused: focused, + failureMessage: "Error loading SVG icon" + ) + continue + } + #endif + imageLoader.loadImage( with: imageSource.request, size: imageSource.size, @@ -272,41 +285,91 @@ public final class TabInfo: NSObject { resizeMode: RCTResizeMode.contain, progressBlock: { _, _ in }, partialLoad: { _ in }, - completionBlock: { error, image in - if error != nil { - print("[TabView] Error loading image: \(error!.localizedDescription)") - return - } - guard let image else { return } - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let icon = image.resizeImageTo(size: iconSize) - #if os(iOS) - if props.experimentalBakedTintColors { - if focused { - props.focusedIcons[index] = icon?.withRenderingMode(.alwaysTemplate) - } else { - props.icons[index] = icon?.withRenderingMode(.alwaysTemplate) - } - } else { - if focused { - props.focusedIcons[index] = icon - } else { - props.icons[index] = icon - } - } - props.iconsRevision += 1 - #else - if focused { - props.focusedIcons[index] = icon - } else { - props.icons[index] = icon - } - props.iconsRevision += 1 - #endif - } - }) + completionBlock: { [weak self] error, image in + self?.handleLoadedIcon( + image, + error: error, + request: imageSource.request, + at: index, + focused: focused + ) + } + ) } } } } + +private extension TabViewProvider { + func handleLoadedIcon( + _ image: PlatformImage?, + error: Error?, + request: URLRequest, + at index: Int, + focused: Bool + ) { + if let error { + #if RN_BOTTOM_TABS_ENABLE_SVG + loadSVGIcon( + with: request, + at: index, + focused: focused, + failureMessage: "Error loading image: \(error.localizedDescription)" + ) + #else + print("[TabView] Error loading image: \(error.localizedDescription)") + #endif + return + } + guard let image else { return } + updateIcon(image, at: index, focused: focused) + } + + func updateIcon(_ image: PlatformImage, at index: Int, focused: Bool) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + let icon = image.resizeImageTo(size: iconSize) + #if os(iOS) + if props.experimentalBakedTintColors { + if focused { + props.focusedIcons[index] = icon?.withRenderingMode(.alwaysTemplate) + } else { + props.icons[index] = icon?.withRenderingMode(.alwaysTemplate) + } + } else { + if focused { + props.focusedIcons[index] = icon + } else { + props.icons[index] = icon + } + } + props.iconsRevision += 1 + #else + if focused { + props.focusedIcons[index] = icon + } else { + props.icons[index] = icon + } + props.iconsRevision += 1 + #endif + } + } + + #if RN_BOTTOM_TABS_ENABLE_SVG + func loadSVGIcon( + with request: URLRequest, + at index: Int, + focused: Bool, + failureMessage: String + ) { + SVGIconLoader.decode(with: request) { [weak self] image in + guard let self else { return } + if let image { + updateIcon(image, at: index, focused: focused) + } else { + print("[TabView] \(failureMessage)") + } + } + } + #endif +} diff --git a/packages/react-native-bottom-tabs/ios/__tests__/svg_configuration_test.rb b/packages/react-native-bottom-tabs/ios/__tests__/svg_configuration_test.rb new file mode 100644 index 00000000..da9a4a6a --- /dev/null +++ b/packages/react-native-bottom-tabs/ios/__tests__/svg_configuration_test.rb @@ -0,0 +1,38 @@ +require "minitest/autorun" +require_relative "../svg_configuration" + +class SVGConfigurationTest < Minitest::Test + def test_svg_support_is_disabled_by_default + $RNBottomTabsEnableSVG = nil + + refute RNBottomTabs::SVGConfiguration.enabled? + assert_equal "ios/SVG/**/*", RNBottomTabs::SVGConfiguration::SVG_SOURCES + assert_equal( + { "DEFINES_MODULE" => "YES" }, + RNBottomTabs::SVGConfiguration.xcconfig({ "DEFINES_MODULE" => "YES" }) + ) + end + + def test_global_flag_enables_svg_support + $RNBottomTabsEnableSVG = true + + assert RNBottomTabs::SVGConfiguration.enabled? + end + + def test_opt_in_sets_compilation_conditions + xcconfig = RNBottomTabs::SVGConfiguration.xcconfig( + { "DEFINES_MODULE" => "YES" }, + enabled: true + ) + + assert_equal "YES", xcconfig["DEFINES_MODULE"] + assert_equal( + "$(inherited) RN_BOTTOM_TABS_ENABLE_SVG=1", + xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] + ) + assert_equal( + "$(inherited) RN_BOTTOM_TABS_ENABLE_SVG", + xcconfig["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] + ) + end +end diff --git a/packages/react-native-bottom-tabs/ios/include/module.modulemap b/packages/react-native-bottom-tabs/ios/include/module.modulemap index 52fb1992..819cfce2 100644 --- a/packages/react-native-bottom-tabs/ios/include/module.modulemap +++ b/packages/react-native-bottom-tabs/ios/include/module.modulemap @@ -1,6 +1,5 @@ module react_native_bottom_tabs { header "../RCTTabViewComponentView.h" header "../RCTBottomAccessoryComponentView.h" - header "../SVG/SvgDecoder.h" export * } diff --git a/packages/react-native-bottom-tabs/ios/react-native-bottom-tabs-Bridging-Header.h b/packages/react-native-bottom-tabs/ios/react-native-bottom-tabs-Bridging-Header.h index b4a33181..956e0ce0 100644 --- a/packages/react-native-bottom-tabs/ios/react-native-bottom-tabs-Bridging-Header.h +++ b/packages/react-native-bottom-tabs/ios/react-native-bottom-tabs-Bridging-Header.h @@ -2,3 +2,7 @@ #import #import #import + +#if RN_BOTTOM_TABS_ENABLE_SVG +#import "SVG/SvgDecoder.h" +#endif diff --git a/packages/react-native-bottom-tabs/ios/svg_configuration.rb b/packages/react-native-bottom-tabs/ios/svg_configuration.rb new file mode 100644 index 00000000..517a580d --- /dev/null +++ b/packages/react-native-bottom-tabs/ios/svg_configuration.rb @@ -0,0 +1,19 @@ +module RNBottomTabs + module SVGConfiguration + SVG_SOURCES = "ios/SVG/**/*" + COMPILATION_CONDITION = "RN_BOTTOM_TABS_ENABLE_SVG" + + def self.enabled? + defined?($RNBottomTabsEnableSVG) && $RNBottomTabsEnableSVG == true + end + + def self.xcconfig(base, enabled: enabled?) + return base unless enabled + + base.merge( + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) #{COMPILATION_CONDITION}=1", + "SWIFT_ACTIVE_COMPILATION_CONDITIONS" => "$(inherited) #{COMPILATION_CONDITION}" + ) + end + end +end diff --git a/packages/react-native-bottom-tabs/package.json b/packages/react-native-bottom-tabs/package.json index 8a25bc6d..51d8c8df 100644 --- a/packages/react-native-bottom-tabs/package.json +++ b/packages/react-native-bottom-tabs/package.json @@ -38,7 +38,7 @@ "!**/.*" ], "scripts": { - "test": "jest", + "test": "ruby ios/__tests__/svg_configuration_test.rb && jest", "typecheck": "tsc -b", "lint": "eslint \"**/*.{js,ts,tsx}\"", "build": "bob build" @@ -112,7 +112,7 @@ }, "codegenConfig": { "name": "RNCTabView", - "type": "all", + "type": "components", "jsSrcsDir": "./src", "android": { "javaPackageName": "com.rcttabview" @@ -121,11 +121,6 @@ "componentProvider": { "RNCTabView": "RCTTabViewComponentView", "BottomAccessoryView": "RCTBottomAccessoryComponentView" - }, - "modulesConformingToProtocol": { - "RCTImageDataDecoder": [ - "SvgDecoder" - ] } } } diff --git a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec index 15fddf33..9ab72772 100644 --- a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec +++ b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec @@ -1,4 +1,5 @@ require "json" +require_relative "ios/svg_configuration" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) @@ -27,9 +28,11 @@ Pod::Spec.new do |s| s.dependency "SwiftUIIntrospect", '~> 1.0' - s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES' - } + s.exclude_files = RNBottomTabs::SVGConfiguration::SVG_SOURCES unless RNBottomTabs::SVGConfiguration.enabled? + + s.pod_target_xcconfig = RNBottomTabs::SVGConfiguration.xcconfig( + { 'DEFINES_MODULE' => 'YES' } + ) install_modules_dependencies(s) end diff --git a/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts b/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts deleted file mode 100644 index dad9f8f7..00000000 --- a/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TurboModuleRegistry, type TurboModule } from 'react-native'; - -export interface Spec extends TurboModule {} - -export default TurboModuleRegistry.getEnforcing('SvgDecoder'); diff --git a/packages/react-native-bottom-tabs/src/expo.ts b/packages/react-native-bottom-tabs/src/expo.ts index 71c3b0d3..74e00033 100644 --- a/packages/react-native-bottom-tabs/src/expo.ts +++ b/packages/react-native-bottom-tabs/src/expo.ts @@ -4,9 +4,12 @@ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const ConfigPlugins = require('@expo/config-plugins'); +const GenerateCode = require('@expo/config-plugins/build/utils/generateCode'); -const { createRunOncePlugin, withAndroidStyles } = +const { createRunOncePlugin, withAndroidStyles, withPodfile } = ConfigPlugins as typeof import('@expo/config-plugins'); +const { mergeContents, removeContents } = + GenerateCode as typeof import('@expo/config-plugins/build/utils/generateCode'); const MATERIAL3_THEME_DYNAMIC = 'Theme.Material3.DynamicColors.DayNight.NoActionBar'; @@ -20,13 +23,21 @@ type ConfigProps = { * Define theme that should be used. * @default 'material3' */ - theme: + theme?: | 'material2' | 'material3' | 'material3-dynamic' | 'material3-expressive'; + /** + * Enable SVG tab icons on Apple platforms. + * @default false + */ + enableSVG?: boolean; }; +const SVG_PODFILE_TAG = 'react-native-bottom-tabs-svg'; +const SVG_PODFILE_FLAG = '$RNBottomTabsEnableSVG = true'; + const withMaterial3Theme: ConfigPlugin = (config, options) => { const theme = options?.theme; @@ -52,7 +63,31 @@ const withMaterial3Theme: ConfigPlugin = (config, options) => { }); }; -export default createRunOncePlugin( - withMaterial3Theme, - 'react-native-bottom-tabs' -); +const withSVGSupport: ConfigPlugin = (config, options) => { + return withPodfile(config, (podfileConfig) => { + const contents = podfileConfig.modResults.contents; + + podfileConfig.modResults.contents = options?.enableSVG + ? mergeContents({ + src: contents, + newSrc: SVG_PODFILE_FLAG, + tag: SVG_PODFILE_TAG, + anchor: /^platform :ios/, + offset: 1, + comment: '#', + }).contents + : removeContents({ + src: contents, + tag: SVG_PODFILE_TAG, + }).contents; + + return podfileConfig; + }); +}; + +const withBottomTabs: ConfigPlugin = (config, options) => { + config = withMaterial3Theme(config, options); + return withSVGSupport(config, options); +}; + +export default createRunOncePlugin(withBottomTabs, 'react-native-bottom-tabs');