From 39d61be67f9b15d43a3b3e16c98f09e6a089f0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 2 Oct 2024 19:56:55 +0200 Subject: [PATCH 1/9] feat: add material bottom tabs reference example --- example/assets/icons/article.png | Bin 0 -> 868 bytes example/assets/icons/article_dark.png | Bin 0 -> 853 bytes example/assets/icons/chat.png | Bin 0 -> 838 bytes example/assets/icons/chat_dark.png | Bin 0 -> 789 bytes example/assets/icons/grid.png | Bin 0 -> 706 bytes example/assets/icons/grid_dark.png | Bin 0 -> 710 bytes example/assets/icons/person.png | Bin 0 -> 1148 bytes example/assets/icons/person_dark.png | Bin 0 -> 1077 bytes example/package.json | 4 +- example/src/App.tsx | 2 + example/src/Examples/MaterialBottomTabs.tsx | 65 +++++++++++++++++ example/visionos/Podfile.lock | 67 +++++++++++++++-- yarn.lock | 77 +++++++++++++++++++- 13 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 example/assets/icons/article.png create mode 100644 example/assets/icons/article_dark.png create mode 100644 example/assets/icons/chat.png create mode 100644 example/assets/icons/chat_dark.png create mode 100644 example/assets/icons/grid.png create mode 100644 example/assets/icons/grid_dark.png create mode 100644 example/assets/icons/person.png create mode 100644 example/assets/icons/person_dark.png create mode 100644 example/src/Examples/MaterialBottomTabs.tsx diff --git a/example/assets/icons/article.png b/example/assets/icons/article.png new file mode 100644 index 0000000000000000000000000000000000000000..1897117cc1799eddd7109ec0bfad71dac4498e83 GIT binary patch literal 868 zcmV-q1DpJbP)Px&9!W$&RCr$Poqtl>Kn#U#j!*^)r)fA@>B&e>10_R`fIIGwX`42AS)LSa;xGU1 zN-Moj+O?fYJPv$H6&OobXYm05#YF%B02C`wygVHMfMNxTm!|{H5y0b@m-}%XKMcdP zD$Z}1rs31=`^)?4ICiZc+5ta=A3pY$Y|jwF0Q4&UuVUyGLI{A~#qSw{4d8LJS&h@h zLvP#8u6TOu1Rp@B`~JRy=m~-i;OY76Ll0LwtsU44ga809Q)}|Q%6aJVG0I=f{>sSx~ zu;&{07!Uv;ZZfhV0N{YTyi5oHH~>KA%^Cm>1ds&*01W^#AON5dKr{pZya7NY1OU7d zKokT37yxEd_i;M_0AMPxWv;#;id~Hk_(x9wY)RtokQ4c6^EpcG0T2SBpG%Kg~` zPzfr5+;R0RN5!Y?Rea^of>eSk0e4&psNz%hDn0;+U+@4xm5@2EDi{E!z{(r|jX$bW zg0_wW006Bu^Y?srk5u+vblQ6H9iSzuouLXu0aVeKUxumxtH%LQOaK4?*snl#g%s;Q zWaq~jAP57XxCHN;M0KN}s`-?GE zQLW>ug6V)(MH}Y>fXC5|C9^f%ZBb?KMW?Ol06IWd$-Wt?f>niitLIy1=U09inlGWZ z0suVyY-<465_%2*;OS>u1HhKha{vHOKie7rwuGL8lLz3X-@mncpA&E&aGI|$i9tKA zZ?50nIlgQFr}7FbN2Fc05r61v1DJzT`(#G)J@}l)*Sp)RtF;qL2!JJaK}7Y>;6eb* zj+O)=O8ig&OMwt6emH>d5H_3D@OrW8<#o2s{`}i^Tu;;VIpejsVOvm4qRaCFfMVtV u06?(<#mmzH04P?VczHSi0L2OvFaHe$x8r{)PzE~y0000Px&4@pEpRCr$Pom+AfF%U(Sl@V43tcD?-*x1*%AC_lwri$W2>TcAYd_p#pO8x2v|&Dad}Pv0u~ckT%HqvfW-tBm*)f=K)~}D zWB&WH{l9oicPale#`yJG>3gA~FJi|JFHS2~h#SKR>o12~h#R zcYfRGTK?D9&%ycmaPt)rK=)t9YoCIMfS;e}Y3Jrn-X_lOpQqsFDOQ)}{8 zR|$yZyt!i@U}EY1Bd|&r@Bl#FC6w+T0WSVA5a2T_jX*#tPCpp}d}gH)2q?wrCqsbG ztTbZx0@MadX$6w0CquP<75`yL*5sluVrl~cW~CvDiCVvkM}W|!03x8Y-_MgFpc$}A zj{rrec^m=FfK_?~C_>HShZoSyuRZv z0u-URFSE9z)#?KiYf*EH;7w1Y9Q&xvec`YL({%Sh(sZBcPtCl@S3JuKLLc zsAp90n^E7f z0AeNu0y6XpVi8aPF_QuT8F~e=2q=JU}o@iaSW-5 zdpkEeNZL@qRdt#`*Y&KVFtJUWELJx$UYueRe#u<9pVwwTsjvELGWET!(kxbn{k2kGJu-wC{*?VV9=^>h zL+HboKUQC-I~)n&|6U#*{@YS0h+%X6PnWDn)}RBIKfjlsQ#Q?O#qZm%^}k=^O86bg z_22g0w_inNlg!sN{QMiQ`r%Z3{gzn^y7-xVj&m3=?s~{kz&Yz9Hr8tK1zaY_<>l6$ z`9azD`%#EKPM-f$pRh?psVW^f^qgJ!Kzhd44ONV*9J0Ug9?as; zdy(`_73jkHAI1)%FPH*8ay4{?zXb{atxdex$H3@u)?sqJBLmYj`@B%&BXb+I=7~=D zqg&0Q(D0sfb?&Lm@aQ#BjSr6pq!&bYxH@c7Wl>1@Co$`cx{a;2g3QMnU`#gXX5{hB z`6F?PH@g0{#Y%<;|96Y|6{Oz0Xz;;4&!t)K?d9-kjQ>9`Z@5)=Z_%56C9jp$d7&|8 zzu7rG?AX^C)SkXptW$k}D?+jH$fT+Vo0-}=tM>2P7bElS)v8DQ&s9R3t5(~J-?x?y z(S3eHZW}A({@h(H91L~ReAW_bVr literal 0 HcmV?d00001 diff --git a/example/assets/icons/chat_dark.png b/example/assets/icons/chat_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c79f74a335da7753d3491eb9f15438692a22b097 GIT binary patch literal 789 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U^?&V;uum9 z_jYb<&|w1}w!4!V)tUX+=J34YtJJWc_{P8QO_}xKFO43X%AZ>B`c!G{3F4d+v^sc? zg2GNtfrf?@rjt)2896-ECdAAW-LRnHc#zlM${%(9^A3qTz3;6l|9s+}Eq%*MS+6ji zU+enr&5!G1g1Sr%@87FzXIani;P=CP_gyL#tTo%Nt3TSKZ!`D%$zAq;et+Uv@cVc5 z$rx5S#{8JKCDJSI#)mg$m)LlcwjQunX`H+B|`En zFZcu&7*8=$a5%J_UD+XBBh{gy#h>4;;atEegS`yd4VT{Vb8K5Dvi9fFdV$Ss6%5WR zS(sF6stlM)+Je|vJbrvhV0po_l!s%&hnELAE=YPwP7_x+Ti2NI+Q|BE@c|~DrHV>$ zqi4i5m^x&BV`tId cS+*@b2=@q7oryCyeXC$p`vD*J-wF1iqrU*u%#wQ7WjvP1C z7nFGz#c$pNl<~bW=iwvv2ysac2^|T>X2$53f(e3Be}2DYjbN5Jlm0_Eam~-A?-{Q# z2qp+@U|2Ohlf$9m3Ug3%)ZJ?Ca%bD(5FUjC3)&f$?qy066krJSEd0(SQlWjsZ$JAQ zhCoSWCXp(}jT{^eE0#DkIO!{hF)|8F72ybY!FohVfnm|4Br8Un#Me>J7uNGL{4@RI zEEl;=lI`Dn;m8xtzipmdJovx2d&c*LXP9OF%f?QRtes}EKAS1+`}M=hEDZY|U)kKM zps=qtW^R8&#E09DU%&qSZgE%rG}*mOvvw&?J1KS3jf-L5VsV5;$SaSW-5 zdpq0H&&83)dH4B-mL30J3J9e$eC7MMNau8?(5&OzCQ1EyBxP5y|D*A$1GOb3Z2K7# zc5pB-G$gXNwI63>kO)htsE|S9ez?xCr@p4n_uS97U!O*p+)Lq%Y^|-c9tH;^ma(LSR zYK9<1CLPu{?K7@g9}Hq-@aU{!X1K6={To?^phfo>7{0vHu4ZqTvcjB!VehX~_i`C- z$IrU`{P=N;$jOlnRV$elfQB$PtY5gvV-Z7W;vB|_d#ofGPV4qDmk7EGS}6SC7hAe- z)dP`+Q}5@+|28eDYSeUKXyIc%@ZtV_kWasxYX8`1pSOO)+s}^$%8fd?2P=@vTzALiF?V63=FTc@>xIgt*B>bbddF${_T}6AH#XJ12O%w>+H%`b8Tfe z5x(;0R{Wu7HE#>HIxyUrRK>~gVEU)yU4g8J#GXi=>tj1m)c%NJ_p-e#G9TXXeR$QF zv9#f@P?+qccbW_g{_8(wFii1&RmwpE^9QfvD~7%UpB3zZiITz7)z4*}Q$iB}G4m;m literal 0 HcmV?d00001 diff --git a/example/assets/icons/grid_dark.png b/example/assets/icons/grid_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7556ecdfd84aa560225665add070319e3d8111ba GIT binary patch literal 710 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U~2GmaSW-5 zdpq-D(Q5+^*17-xzxcM)*jG(Xej#&X&-Z;%3R}2~Uj4TCaoy{|#a-63>=^6Jdx7@t|QF|KOe(e#hAfc5BZ)zU)(4Grc@dF?wtdUleQ2`}xE=?fJei*|d57ZL4Q3zaDPK zIBCI721Ydpa~VcGM`JlGtbgB_eCk^4g#+%gtW9F#&|q70N!dYAtFEsh>426klgPqH z&k=(AloNh^|Mv3h$N!9zXU*a~v0X4pD(X-8>o0=yMp-GPG$O(+cKj~te92z zCYK?vuG@ifjvEty)fjyh3jKMv!qw7rztx#Y! zOs##atZIg(ER~$>C-m$3(wLZh$`cG3JHrcF8yfzvUAijmszUT^wx*C;{gqDL;_DYU ztc~^#J=?YmrmexHxcL`?`c9B0Y4|r*BMNEAE-De6?(M z(a#$@1wR;Gc`mKUz<8Er*7kHJfeSthDy!AdxCU@`M_aSW-5 zdpoB#=(d5t(Y0ULQWq4=YMU(T=T;@9!L!%#ao?L67RNfnR;$0Ss`$gm6QTX{?>Fa3 zYg^jlmvOONzIU_Inc0IXeZpURc>JD5jA)>Ptbwk z7?(u(c@71JWY(VD=_~>bF^$i+Z_mxx|JQ0;#`eGO6UFy$&;4!vZ_n?-uL|3jJNvk4LjmuDpXYx&bDTZ#IQ3Eg`cHg^bQ`8$-|z1``T5u9A6xFeJ^TH4 zP0iQn9n1@SErnLz+OU8B?tSlGwNBSBXNZftf4l0@k8YN5h7Fm|W!&Ng?l3rr#AH6H z&^nXHa3a|MM(O9jXY0(Le2y@m^*3#{aq_1i{uqV{Rr`+=FWvU&(b>aiPOvg`e)m2i zA2dPa(A%?9fBv1#@1PXc_rp@aX34@(h6`0$Rbi>~+s-g9ak*5n--e50fhUtj#NKbK zU!9%&T$hVou%PnkYG3>27b}_Kv*LGsv)HYpV;XR4-!_M>Z>Qhe-^{UN&+AB|;6yH= z9qyahL~J5F{R<;oCcExPIXcVBUe#EqF~3vK(BJW;iQxRN&+OxTs`sB;=W(^C`wZie zjmvCI<0dmZD%EYtv}Nu}E?{!__9KwVQET6kur}q@`lXBxliH4zOB|hjzmLy;>G_Ga z75is%bTCh>T6gQJ8}HtV{n`w*x)YWzGUE6aeE#E)*d+@-He@+}m{iqPUB<4kUGBtU z#W%M;yq~y!DTnA`x2E-59$T^YvbSrTnaA~R&ikF-j>XMa8b8b|TK0WA7sKHN${l&H z#2(e`kCvamXWqTPM-%7mpHdpm>JVWURw~IT@mL9(My?B9*!x+s``yW%8yq*jeAxSM z&W+>s{Lhabn_YG+>U`OIwgVqoqks0L?OV_;UM(y8#F#bUvq=w&-}be+JjsO&*A~rZ zzwCbelDf7ym{}B5p+|ke9w(C!(ZbwuAkHX(?Oll#fE0=CLEA%}~mf?`MSL>B+ z{{DKW=5jMEc&BK+wqixu%3oo>mdP@zcnC6_nmVStOqTp00i_>zopr0H5p#GXMYp literal 0 HcmV?d00001 diff --git a/example/assets/icons/person_dark.png b/example/assets/icons/person_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a24a68075253170a2158eeb682c9323287b680f6 GIT binary patch literal 1077 zcmV-51j_q~P)Px&?@2^KRCr$PoMCbsAq<5#Cu@4LwkJz_vNmLv4nu59AU*4C=J}7~u@LY+Nx*tH zFNa>G3cQr|uFekuC=LPv5P)I@in~_^2tcs{#oa3d1fW=f;_j6J0#K|#arepq0Vr0W zxO-)Q02C`w+`TeD0G1SZJ&xnY(4u{AuixkUpY#3alGj4xD`Y?yy^HOY>({#>JqUIH zO~5hYz2c8{)F7AvyuFu})u_J*sFkSiJM_?P7y;~-9FJBFLSHXD_HY|U0DpnArpp92 z0GVay66J^qK*oaWO&*$snF)Zm^t3Y4_WSL<*Ymb+%x44(2E}^70>B&np2+q3qF4`D z0C=L`BYuy1FNE^>mk;1Pty+hz74cUfl$W8a0N5R;wW}&GL%S2R7Pyt~Es=Jj-tqmv7Del|u6g!UiOfB}F?PzYhXu?0Yj za9rbu9xMQnor+a{B5+e=VJJb?PNhwBM<~x}c*k#w@ZAXmfHoE@Qdc2(#BV;!=U+a6 zGZL&a)C0y7v0m{#zEY$8mNOOro-#C!zlnG2a`d-_-;pWiPBDm6S#RWKL?uP^Q`Epo zO>ZfRK#1(BBK_WM%aj3qfM^I*bEX>1Nnmw7@J3~e{JwU0+raG@0U$Dgs_cwZnE|wM zW-QD!3oi3E)JpXo5R@fno+x_;%;W8MJGcFQd(F3}_Y*VK?T3$|>uLb3Ou5|m0#J-h z00f{|f#UAjGoUZBw{F~;AKsW3-u}GUc+sv{?$6yb0q8f_u3XVv6|~>j+s_!Q?bGe6 z0U)1-@rcz9X{9hK091>94@}RFr#@NvEC8&c--GeA%NJ+>R}+2)0xwZ009O}&1_8US zgaEK};mosOGT5Y-rfdM&;*b9WV&3P^pT~2-__y`&$12!V2$=vf#P6xzM9IFQnR(7g zgOCLv@-re*^heR%x0|~e5_xXn|G2UMtVTaU+9s-D=RV&f00000NkvXXu0mjfhG^y) literal 0 HcmV?d00001 diff --git a/example/package.json b/example/package.json index a15fe9c2..05ad2a03 100644 --- a/example/package.json +++ b/example/package.json @@ -22,8 +22,10 @@ "react": "18.3.1", "react-native": "0.75.3", "react-native-gesture-handler": "^2.20.0", + "react-native-paper": "^5.12.5", "react-native-safe-area-context": "^4.11.0", - "react-native-screens": "^3.34.0" + "react-native-screens": "^3.34.0", + "react-native-vector-icons": "^10.2.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index 14836362..bfadd950 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -18,6 +18,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'; import JSBottomTabs from './Examples/JSBottomTabs'; import ThreeTabs from './Examples/ThreeTabs'; import FourTabs from './Examples/FourTabs'; +import MaterialBottomTabs from './Examples/MaterialBottomTabs'; const examples = [ { component: ThreeTabs, name: 'Three Tabs' }, @@ -28,6 +29,7 @@ const examples = [ screenOptions: { headerShown: false }, }, { component: JSBottomTabs, name: 'JS Bottom Tabs' }, + { component: MaterialBottomTabs, name: 'Material (JS) Bottom Tabs' }, ]; function App() { diff --git a/example/src/Examples/MaterialBottomTabs.tsx b/example/src/Examples/MaterialBottomTabs.tsx new file mode 100644 index 00000000..986ffd1b --- /dev/null +++ b/example/src/Examples/MaterialBottomTabs.tsx @@ -0,0 +1,65 @@ +import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation'; +import { Article } from '../Screens/Article'; +import { Albums } from '../Screens/Albums'; +import { Contacts } from '../Screens/Contacts'; +import { Chat } from '../Screens/Chat'; +import { Image, type ImageSourcePropType } from 'react-native'; + +const Tab = createMaterialBottomTabNavigator(); + +const TabBarIcon = ({ source }: { source: ImageSourcePropType }) => ( + +); + +function MaterialBottomTabs() { + return ( + + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); +} + +export default MaterialBottomTabs; diff --git a/example/visionos/Podfile.lock b/example/visionos/Podfile.lock index f5af0161..6cb235ef 100644 --- a/example/visionos/Podfile.lock +++ b/example/visionos/Podfile.lock @@ -1243,7 +1243,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-swiftui-tabview (0.1.0): + - react-native-bottom-tabs (0.1.0): - DoubleConversion - glog - RCT-Folly (= 2024.01.01.00) @@ -1264,6 +1264,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-safe-area-context (4.11.0): + - React-Core - React-nativeconfig (0.75.0) - React-NativeModulesApple (0.75.0): - glog @@ -1553,6 +1555,49 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) + - RNGestureHandler (2.20.0): + - DoubleConversion + - glog + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNScreens (3.34.0): + - DoubleConversion + - glog + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - SocketRocket (0.7.0.1) - Yoga (0.0.0) @@ -1593,7 +1638,8 @@ DEPENDENCIES: - "React-logger (from `../node_modules/@callstack/react-native-visionos/ReactCommon/logger`)" - "React-Mapbuffer (from `../node_modules/@callstack/react-native-visionos/ReactCommon`)" - "React-microtasksnativemodule (from `../node_modules/@callstack/react-native-visionos/ReactCommon/react/nativemodule/microtasks`)" - - react-native-swiftui-tabview (from `../..`) + - react-native-bottom-tabs (from `../..`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "React-nativeconfig (from `../node_modules/@callstack/react-native-visionos/ReactCommon`)" - "React-NativeModulesApple (from `../node_modules/@callstack/react-native-visionos/ReactCommon/react/nativemodule/core/platform/ios`)" - "React-perflogger (from `../node_modules/@callstack/react-native-visionos/ReactCommon/reactperflogger`)" @@ -1625,6 +1671,8 @@ DEPENDENCIES: - "ReactNativeHost (from `../node_modules/@rnx-kit/react-native-host`)" - ReactTestApp-DevSupport (from `../node_modules/react-native-test-app`) - ReactTestApp-Resources (from `..`) + - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - RNScreens (from `../node_modules/react-native-screens`) - "SocketRocket (from `../node_modules/@callstack/react-native-visionos/third-party-podspecs/SocketRocket.podspec`)" - "Yoga (from `../node_modules/@callstack/react-native-visionos/ReactCommon/yoga`)" @@ -1697,8 +1745,10 @@ EXTERNAL SOURCES: :path: "../node_modules/@callstack/react-native-visionos/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/@callstack/react-native-visionos/ReactCommon/react/nativemodule/microtasks" - react-native-swiftui-tabview: + react-native-bottom-tabs: :path: "../.." + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" React-nativeconfig: :path: "../node_modules/@callstack/react-native-visionos/ReactCommon" React-NativeModulesApple: @@ -1761,6 +1811,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-test-app" ReactTestApp-Resources: :path: ".." + RNGestureHandler: + :path: "../node_modules/react-native-gesture-handler" + RNScreens: + :path: "../node_modules/react-native-screens" SocketRocket: :podspec: "../node_modules/@callstack/react-native-visionos/third-party-podspecs/SocketRocket.podspec" Yoga: @@ -1801,7 +1855,8 @@ SPEC CHECKSUMS: React-logger: 3b9cc828d9dd5f8ba7da014dab19fcff6beceb79 React-Mapbuffer: b925e985164304a420b29103c38c6c9d91691d2d React-microtasksnativemodule: 1940f8375742bf1fa2100cbc1dba3e9b20c1b8b5 - react-native-swiftui-tabview: 3be89c5b1d6efe6643ce44ee9aa3ce4bf7760320 + react-native-bottom-tabs: 894d1fb8fc4e6d525b2da35e83e00e18c420cdf2 + react-native-safe-area-context: 851c62c48dce80ccaa5637b6aa5991a1bc36eca9 React-nativeconfig: afe30c46ab7d8b251f38961a644698387f5f9e6d React-NativeModulesApple: ccef48a493eae993fcddc7c7b7f80e5cdd2add8a React-perflogger: 76276a729b69a8131bf2a56f9edb811448ea1408 @@ -1833,9 +1888,11 @@ SPEC CHECKSUMS: ReactNativeHost: 99c0ffb175cd69de2ac9a70892cd22dac65ea79d ReactTestApp-DevSupport: b7cd76a3aeee6167f5e14d82f09685059152c426 ReactTestApp-Resources: 0be6dec7d289172429c080c56dbd4f5aeb5e1ced + RNGestureHandler: 18b9b5d65c77c4744a640f69b7fccdd47ed935c0 + RNScreens: 5288a8dbeedb3c5051aa2d5658c1c553c050b80a SocketRocket: 0ba3e799f983d2dfa878777017659ef6c866e5c6 Yoga: 7c17e3a94a522222e393c1e9c9387afd9428f6eb -PODFILE CHECKSUM: ef8414f59a2b45c9341c759274184990c6e69561 +PODFILE CHECKSUM: f89ba4e3c51d912721e2bf52a0d5575eb0309fc2 COCOAPODS: 1.15.2 diff --git a/yarn.lock b/yarn.lock index 8a1f3060..7c920097 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1684,6 +1684,18 @@ __metadata: languageName: node linkType: hard +"@callstack/react-theme-provider@npm:^3.0.9": + version: 3.0.9 + resolution: "@callstack/react-theme-provider@npm:3.0.9" + dependencies: + deepmerge: ^3.2.0 + hoist-non-react-statics: ^3.3.0 + peerDependencies: + react: ">=16.3.0" + checksum: f888ef0b8f993d393a9d49864f75b04b5cf7259e72f290ec570fc5e314315f75c979b03b391243a72578ab55db4ffcdd4dc9dc1a2f8ec2b516cabb958971a85e + languageName: node + linkType: hard + "@commitlint/cli@npm:^17.8.1": version: 17.8.1 resolution: "@commitlint/cli@npm:17.8.1" @@ -5121,7 +5133,7 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0": +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -5153,7 +5165,7 @@ __metadata: languageName: node linkType: hard -"color-string@npm:^1.9.0": +"color-string@npm:^1.6.0, color-string@npm:^1.9.0": version: 1.9.1 resolution: "color-string@npm:1.9.1" dependencies: @@ -5163,6 +5175,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^3.1.2": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: ^1.9.3 + color-string: ^1.6.0 + checksum: f81220e8b774d35865c2561be921f5652117638dcda7ca4029262046e37fc2444ac7bbfdd110cf1fd9c074a4ee5eda8f85944ffbdda26186b602dd9bb05f6400 + languageName: node + linkType: hard + "color@npm:^4.2.3": version: 4.2.3 resolution: "color@npm:4.2.3" @@ -5842,6 +5864,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^3.2.0": + version: 3.3.0 + resolution: "deepmerge@npm:3.3.0" + checksum: 4322195389e0170a0443c07b36add19b90249802c4b47b96265fdc5f5d8beddf491d5e50cbc5bfd65f85ccf76598173083863c202f5463b3b667aca8be75d5ac + languageName: node + linkType: hard + "deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -11478,9 +11507,11 @@ __metadata: react-native: 0.75.3 react-native-builder-bob: ^0.30.2 react-native-gesture-handler: ^2.20.0 + react-native-paper: ^5.12.5 react-native-safe-area-context: ^4.11.0 react-native-screens: ^3.34.0 react-native-test-app: ^3.10.10 + react-native-vector-icons: ^10.2.0 languageName: unknown linkType: soft @@ -11561,6 +11592,22 @@ __metadata: languageName: node linkType: hard +"react-native-paper@npm:^5.12.5": + version: 5.12.5 + resolution: "react-native-paper@npm:5.12.5" + dependencies: + "@callstack/react-theme-provider": ^3.0.9 + color: ^3.1.2 + use-latest-callback: ^0.1.5 + peerDependencies: + react: "*" + react-native: "*" + react-native-safe-area-context: "*" + react-native-vector-icons: "*" + checksum: 35ce56e0bb19c46ce85adc47a504527b7ec455d1a15f3ace0d6fd438abd51b1fe30e1b44351950e16d6376ddbc18ecc94b558e267767d0a9fd886a5f66a4c322 + languageName: node + linkType: hard + "react-native-safe-area-context@npm:^4.11.0": version: 4.11.0 resolution: "react-native-safe-area-context@npm:4.11.0" @@ -11620,6 +11667,21 @@ __metadata: languageName: node linkType: hard +"react-native-vector-icons@npm:^10.2.0": + version: 10.2.0 + resolution: "react-native-vector-icons@npm:10.2.0" + dependencies: + prop-types: ^15.7.2 + yargs: ^16.1.1 + bin: + fa-upgrade.sh: bin/fa-upgrade.sh + fa5-upgrade: bin/fa5-upgrade.sh + fa6-upgrade: bin/fa6-upgrade.sh + generate-icon: bin/generate-icon.js + checksum: fda930df4e63f12533268f5b339ebe4c77c691eae43503328466b3087ed868a06a4593fd246e75ac6b5ec955543eec35608c7922191bdcc3b3a94ed7f3575ef0 + languageName: node + linkType: hard + "react-native@npm:0.75.3": version: 0.75.3 resolution: "react-native@npm:0.75.3" @@ -13670,6 +13732,15 @@ __metadata: languageName: node linkType: hard +"use-latest-callback@npm:^0.1.5": + version: 0.1.11 + resolution: "use-latest-callback@npm:0.1.11" + peerDependencies: + react: ">=16.8" + checksum: cc6df404a4ed3a39d0eb014a815c2e568a6abbe8c5ff09f9205a08bf68e86201826ed633865a6056ca0fd9581e0e7e70f18ca26c592a9eaccea5d244cf283b1f + languageName: node + linkType: hard + "use-latest-callback@npm:^0.2.1": version: 0.2.1 resolution: "use-latest-callback@npm:0.2.1" @@ -14129,7 +14200,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.2.0": +"yargs@npm:^16.1.1, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: From 36702550e5417bc5545838801f4026e6c79f833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 2 Oct 2024 20:05:32 +0200 Subject: [PATCH 2/9] feat: implement custom image handling on iOS --- example/package.json | 3 +- example/src/Examples/FourTabs.tsx | 22 +++++++++++--- ios/RCTTabViewViewManager.mm | 5 +++- ios/TabViewImpl.swift | 17 ++++++++++- ios/TabViewProvider.swift | 50 +++++++++++++++++++++++++++---- src/TabView.tsx | 36 +++++++++++----------- src/TabViewNativeComponent.ts | 9 +++--- src/types.ts | 17 +++++++++++ yarn.lock | 18 +---------- 9 files changed, 126 insertions(+), 51 deletions(-) create mode 100644 src/types.ts diff --git a/example/package.json b/example/package.json index 05ad2a03..02ebf8de 100644 --- a/example/package.json +++ b/example/package.json @@ -24,8 +24,7 @@ "react-native-gesture-handler": "^2.20.0", "react-native-paper": "^5.12.5", "react-native-safe-area-context": "^4.11.0", - "react-native-screens": "^3.34.0", - "react-native-vector-icons": "^10.2.0" + "react-native-screens": "^3.34.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/example/src/Examples/FourTabs.tsx b/example/src/Examples/FourTabs.tsx index 6ec946a9..730a4980 100644 --- a/example/src/Examples/FourTabs.tsx +++ b/example/src/Examples/FourTabs.tsx @@ -8,15 +8,29 @@ import { Chat } from '../Screens/Chat'; export default function FourTabs() { const [index, setIndex] = useState(0); const [routes] = useState([ - { key: 'article', title: 'Article', icon: 'document.fill', badge: '!' }, + { + key: 'article', + title: 'Article', + focusedIcon: require('../../assets/icons/article.png'), + unfocusedIcon: require('../../assets/icons/chat.png'), + badge: '!', + }, { key: 'albums', title: 'Albums', - icon: 'square.grid.2x2.fill', + focusedIcon: require('../../assets/icons/grid.png'), badge: '5', }, - { key: 'contacts', title: 'Contacts', icon: 'person.fill' }, - { key: 'chat', title: 'Chat', icon: 'keyboard' }, + { + key: 'contacts', + focusedIcon: require('../../assets/icons/person.png'), + title: 'Contacts', + }, + { + key: 'chat', + focusedIcon: require('../../assets/icons/chat.png'), + title: 'Chat', + }, ]); const renderScene = SceneMap({ diff --git a/ios/RCTTabViewViewManager.mm b/ios/RCTTabViewViewManager.mm index 4191df3c..52668c3b 100644 --- a/ios/RCTTabViewViewManager.mm +++ b/ios/RCTTabViewViewManager.mm @@ -1,5 +1,6 @@ #import #import +#import #import "RCTBridge.h" #import "react_native_bottom_tabs-Swift.h" @@ -12,12 +13,14 @@ @implementation RCTTabView - (UIView *)view { - return [[TabViewProvider alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + RCTImageLoader *imageLoader = [self.bridge moduleForClass:[RCTImageLoader class]]; + return [[TabViewProvider alloc] initWithEventDispatcher:self.bridge.eventDispatcher imageLoader:imageLoader]; } RCT_EXPORT_VIEW_PROPERTY(items, NSArray) RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(selectedPage, NSString) RCT_EXPORT_VIEW_PROPERTY(tabViewStyle, NSString) +RCT_EXPORT_VIEW_PROPERTY(icons, NSArray); @end diff --git a/ios/TabViewImpl.swift b/ios/TabViewImpl.swift index 736e85fa..fe645fa8 100644 --- a/ios/TabViewImpl.swift +++ b/ios/TabViewImpl.swift @@ -1,5 +1,6 @@ import Foundation import SwiftUI +import React /** Props that component accepts. SwiftUI view gets re-rendered when ObservableObject changes. @@ -9,6 +10,7 @@ class TabViewProps: ObservableObject { @Published var items: TabData? @Published var selectedPage: String? @Published var tabViewStyle: String? + @Published var icons: [Int: UIImage] = [:] } /** @@ -35,10 +37,11 @@ struct TabViewImpl: View { ForEach(props.children?.indices ?? 0..<0, id: \.self) { index in let child = props.children?[safe: index] ?? UIView() let tabData = props.items?.tabs[safe: index] + let icon = props.icons[index] RepresentableView(view: child) .frame(width: child.frame.width, height: child.frame.height) .tabItem { - Label(tabData?.title ?? "", systemImage: tabData?.icon ?? "") + TabItem(icon: icon, title: tabData?.title) } .tag(tabData?.key) .tabBadge(tabData?.badge) @@ -51,6 +54,18 @@ struct TabViewImpl: View { } } +struct TabItem: View { + var icon: UIImage? + var title: String? + + var body: some View { + if let icon { + Image(uiImage: icon) + } + Text(title ?? "") + } +} + extension View { @ViewBuilder func getTabViewStyle(name: String) -> some View { diff --git a/ios/TabViewProvider.swift b/ios/TabViewProvider.swift index c64caecf..e13ba668 100644 --- a/ios/TabViewProvider.swift +++ b/ios/TabViewProvider.swift @@ -1,9 +1,9 @@ import Foundation import SwiftUI +import React struct TabInfo: Codable { let key: String - let icon: String let title: String let badge: String } @@ -16,28 +16,41 @@ struct TabData: Codable { var props = TabViewProps() private var hostingController: UIHostingController? private var coalescingKey: UInt16 = 0 - var eventDispatcher: RCTEventDispatcherProtocol? + private var eventDispatcher: RCTEventDispatcherProtocol? + private var imageLoader: RCTImageLoaderProtocol? + private var iconSize = CGSize(width: 25, height: 25) + @objc var onPageSelected: RCTDirectEventBlock? + + @objc var icons: NSArray? { + didSet { + loadIcons(icons) + } + } + @objc var selectedPage: NSString? { didSet { props.selectedPage = selectedPage as? String } } + @objc var tabViewStyle: NSString? { didSet { props.tabViewStyle = tabViewStyle as? String } } + @objc var items: NSArray? { didSet { props.items = parseTabData(from: items) } } - @objc public convenience init(eventDispatcher: RCTEventDispatcherProtocol) { + @objc public convenience init(eventDispatcher: RCTEventDispatcherProtocol, imageLoader: RCTImageLoader) { self.init() self.eventDispatcher = eventDispatcher + self.imageLoader = imageLoader } public override func didUpdateReactSubviews() { @@ -79,7 +92,35 @@ struct TabData: Codable { return nil } - func parseTabData(from array: NSArray?) -> TabData? { + private func loadIcons(_ icons: NSArray?) { + // TODO: Diff the arrays and update only changed items. + // Now if the user passes `unfocusedIcon` we update every item. + if let imageSources = icons as? [RCTImageSource?] { + for (index, imageSource) in imageSources.enumerated() { + guard let imageSource, let imageLoader else { continue } + imageLoader.loadImage( + with: imageSource.request, + size: iconSize, + scale: imageSource.scale, + clipped: true, + 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 { + self.props.icons[index] = image + } + }) + } + } + } + + private func parseTabData(from array: NSArray?) -> TabData? { guard let array else { return nil } var items: [TabInfo] = [] @@ -88,7 +129,6 @@ struct TabData: Codable { items.append( TabInfo( key: itemDict["key"] as? String ?? "", - icon: itemDict["icon"] as? String ?? "", title: itemDict["title"] as? String ?? "", badge: itemDict["badge"] as? String ?? "" ) diff --git a/src/TabView.tsx b/src/TabView.tsx index 7e76ecaa..7b3c07e3 100644 --- a/src/TabView.tsx +++ b/src/TabView.tsx @@ -1,21 +1,12 @@ -import React from 'react'; import type { TabViewItems } from './TabViewNativeComponent'; -import { Platform, StyleSheet, View } from 'react-native'; +import { Image, Platform, StyleSheet, View } from 'react-native'; + +//@ts-ignore +import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; import TabViewAdapter from './TabViewAdapter'; import useLatestCallback from 'use-latest-callback'; - -export type BaseRoute = { - key: string; - title?: string; - badge?: string; - lazy?: boolean; - icon?: string; -}; - -type NavigationState = { - index: number; - routes: Route[]; -}; +import { useState } from 'react'; +import type { BaseRoute, NavigationState } from './types'; interface Props { navigationState: NavigationState; @@ -47,7 +38,7 @@ const TabView = ({ /** * List of loaded tabs, tabs will be loaded when navigated to. */ - const [loaded, setLoaded] = React.useState([focusedKey]); + const [loaded, setLoaded] = useState([focusedKey]); if (!loaded.includes(focusedKey)) { // Set the current tab to be loaded if it was not loaded before @@ -57,10 +48,20 @@ const TabView = ({ const items: TabViewItems = navigationState.routes.map((route) => ({ key: route.key, title: route.title ?? route.key, - icon: route.icon, badge: route.badge, })); + const icons: ImageSource[] = navigationState.routes + .map((route) => + route.unfocusedIcon + ? route.key === focusedKey + ? route.focusedIcon + : route.unfocusedIcon + : route.focusedIcon + ) + // Pass empty object for icons that are not provided to avoid index mismatch on native side. + .map((icon) => (icon ? Image.resolveAssetSource(icon) : { uri: '' })); + const jumpTo = useLatestCallback((key: string) => { const index = navigationState.routes.findIndex( (route) => route.key === key @@ -73,6 +74,7 @@ const TabView = ({ { const index = navigationState.routes.findIndex((r) => r.key === key); diff --git a/src/TabViewNativeComponent.ts b/src/TabViewNativeComponent.ts index aaa1ce26..7e854758 100644 --- a/src/TabViewNativeComponent.ts +++ b/src/TabViewNativeComponent.ts @@ -4,6 +4,8 @@ import type { DirectEventHandler, WithDefault, } from 'react-native/Libraries/Types/CodegenTypes'; +//@ts-ignore +import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; export type OnPageSelectedEventData = Readonly<{ key: string; @@ -12,7 +14,7 @@ export type OnPageSelectedEventData = Readonly<{ export type TabViewItems = ReadonlyArray<{ key: string; title: string; - icon?: string; + sfSymbol?: string; badge?: string; }>; @@ -21,8 +23,7 @@ export interface TabViewProps extends ViewProps { selectedPage: string; onPageSelected?: DirectEventHandler; tabViewStyle?: WithDefault<'automatic' | 'sidebarAdaptable', 'automatic'>; + icons?: ReadonlyArray; } -export default codegenNativeComponent('RCTTabView', { - excludedPlatforms: ['android'], -}); +export default codegenNativeComponent('RCTTabView'); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..35f76f92 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,17 @@ +import type { ImageSourcePropType } from 'react-native'; + +export type IconSource = string | ImageSourcePropType; + +export type BaseRoute = { + key: string; + title?: string; + badge?: string; + lazy?: boolean; + focusedIcon?: ImageSourcePropType; + unfocusedIcon?: ImageSourcePropType; +}; + +export type NavigationState = { + index: number; + routes: Route[]; +}; diff --git a/yarn.lock b/yarn.lock index 7c920097..8f4c7dd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11511,7 +11511,6 @@ __metadata: react-native-safe-area-context: ^4.11.0 react-native-screens: ^3.34.0 react-native-test-app: ^3.10.10 - react-native-vector-icons: ^10.2.0 languageName: unknown linkType: soft @@ -11667,21 +11666,6 @@ __metadata: languageName: node linkType: hard -"react-native-vector-icons@npm:^10.2.0": - version: 10.2.0 - resolution: "react-native-vector-icons@npm:10.2.0" - dependencies: - prop-types: ^15.7.2 - yargs: ^16.1.1 - bin: - fa-upgrade.sh: bin/fa-upgrade.sh - fa5-upgrade: bin/fa5-upgrade.sh - fa6-upgrade: bin/fa6-upgrade.sh - generate-icon: bin/generate-icon.js - checksum: fda930df4e63f12533268f5b339ebe4c77c691eae43503328466b3087ed868a06a4593fd246e75ac6b5ec955543eec35608c7922191bdcc3b3a94ed7f3575ef0 - languageName: node - linkType: hard - "react-native@npm:0.75.3": version: 0.75.3 resolution: "react-native@npm:0.75.3" @@ -14200,7 +14184,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.1.1, yargs@npm:^16.2.0": +"yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: From f51b193fd752fee9451a8267d93df2d254625df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 2 Oct 2024 22:44:50 +0200 Subject: [PATCH 3/9] feat: implement custom image handling on Android --- .../main/java/com/rcttabview/RCTTabView.kt | 70 ++++++++++++++++--- .../com/rcttabview/RCTTabViewViewManager.kt | 7 +- example/src/Examples/MaterialBottomTabs.tsx | 10 +-- example/src/Examples/ThreeTabs.tsx | 16 ++++- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/android/src/main/java/com/rcttabview/RCTTabView.kt b/android/src/main/java/com/rcttabview/RCTTabView.kt index 152037aa..89e21740 100644 --- a/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -1,15 +1,28 @@ package com.rcttabview +import android.annotation.SuppressLint import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.net.Uri import android.view.Choreographer import android.view.MenuItem -import androidx.appcompat.content.res.AppCompatResources +import com.facebook.common.references.CloseableReference +import com.facebook.datasource.DataSources +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.image.CloseableBitmap +import com.facebook.imagepipeline.request.ImageRequestBuilder import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.WritableMap +import com.facebook.react.views.imagehelper.ImageSource +import com.facebook.react.views.imagehelper.ImageSource.Companion.getTransparentBitmapImageSource import com.google.android.material.bottomnavigation.BottomNavigationView + class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) { private val ANIMATION_DURATION: Long = 300 + private val icons: MutableMap = mutableMapOf() var items: MutableList? = null var onTabSelectedListener: ((WritableMap) -> Unit)? = null @@ -62,17 +75,10 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context fun updateItems(items: MutableList) { this.items = items - // TODO: This doesn't work with hot reload. It clears all menu items - menu.clear() items.forEachIndexed {index, item -> - val menuItem = menu.add(0, index, 0, item.title) - val iconResourceId = resources.getIdentifier( - item.icon, "drawable", context.packageName - ) - if (iconResourceId != 0) { - menuItem.icon = AppCompatResources.getDrawable(context, iconResourceId) - } else { - menuItem.setIcon(android.R.drawable.btn_star) // fallback icon + val menuItem = getOrCreateItem(index, item.title) + if (icons.containsKey(index)) { + menuItem.icon = getDrawable(icons[index]!!) } if (item.badge.isNotEmpty()) { val badge = this.getOrCreateBadge(index) @@ -84,6 +90,48 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context } } + private fun getOrCreateItem(index: Int, title: String): MenuItem { + return menu.findItem(index) ?: menu.add(0, index, 0, title) + } + + fun setIcons(icons: ReadableArray?) { + if (icons == null || icons.size() == 0) { + return + } + + for (idx in 0 until icons.size()) { + val source = icons.getMap(idx) + var imageSource = + ImageSource( + context, + source.getString("uri"), + source.getDouble("width"), + source.getDouble("height")) + if (Uri.EMPTY == imageSource.uri) { + imageSource = getTransparentBitmapImageSource(context) + } + this.icons[idx] = imageSource + + // Update existing item if exists. + menu.findItem(idx)?.let { menuItem -> + menuItem.icon = getDrawable(imageSource) + } + } + } + + @SuppressLint("UseCompatLoadingForDrawables") + private fun getDrawable(imageSource: ImageSource): Drawable { + val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build() + val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context) + val result = DataSources.waitForFinalResult(dataSource) as CloseableReference + val bitmap = result.get().underlyingBitmap + + CloseableReference.closeSafely(result) + dataSource.close() + + return BitmapDrawable(resources, bitmap) + } + // Fixes issues with BottomNavigationView children layouting. private fun measureAndLayout() { measure( diff --git a/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt b/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt index ffa98afd..bb451df0 100644 --- a/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt +++ b/android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt @@ -18,7 +18,6 @@ import com.facebook.yoga.YogaNode data class TabInfo( val key: String, - val icon: String, val title: String, val badge: String ) @@ -40,7 +39,6 @@ class RCTTabViewViewManager : itemsArray.add( TabInfo( key = item.getString("key") ?: "", - icon = item.getString("icon") ?: "", title = item.getString("title") ?: "", badge = item.getString("badge") ?: "" ) @@ -57,6 +55,11 @@ class RCTTabViewViewManager : } } + @ReactProp(name = "icons") + fun setIcons(view: ReactBottomNavigationView, icons: ReadableArray?) { + view.setIcons(icons) + } + public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView { eventDispatcher = context.getNativeModule(UIManagerModule::class.java)!!.eventDispatcher val view = ReactBottomNavigationView(context) diff --git a/example/src/Examples/MaterialBottomTabs.tsx b/example/src/Examples/MaterialBottomTabs.tsx index 986ffd1b..1313506b 100644 --- a/example/src/Examples/MaterialBottomTabs.tsx +++ b/example/src/Examples/MaterialBottomTabs.tsx @@ -30,9 +30,7 @@ function MaterialBottomTabs() { component={Albums} options={{ tabBarIcon: () => ( - + ), }} /> @@ -42,7 +40,7 @@ function MaterialBottomTabs() { options={{ tabBarIcon: () => ( ), }} @@ -52,9 +50,7 @@ function MaterialBottomTabs() { component={Chat} options={{ tabBarIcon: () => ( - + ), }} /> diff --git a/example/src/Examples/ThreeTabs.tsx b/example/src/Examples/ThreeTabs.tsx index 87ea997c..c71bbfd7 100644 --- a/example/src/Examples/ThreeTabs.tsx +++ b/example/src/Examples/ThreeTabs.tsx @@ -7,14 +7,24 @@ import { Contacts } from '../Screens/Contacts'; export default function ThreeTabs() { const [index, setIndex] = useState(0); const [routes] = useState([ - { key: 'article', title: 'Article', icon: 'document.fill', badge: '!' }, + { + key: 'article', + title: 'Article', + focusedIcon: require('../../assets/icons/article.png'), + unfocusedIcon: require('../../assets/icons/chat.png'), + badge: '!', + }, { key: 'albums', title: 'Albums', - icon: 'square.grid.2x2.fill', + focusedIcon: require('../../assets/icons/grid.png'), badge: '5', }, - { key: 'contacts', title: 'Contacts', icon: 'person.fill' }, + { + key: 'contacts', + focusedIcon: require('../../assets/icons/person.png'), + title: 'Contacts', + }, ]); const renderScene = SceneMap({ From e263f6b3d88f9ec00aa8e220b3974b1314709e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 3 Oct 2024 15:20:24 +0200 Subject: [PATCH 4/9] fix: native iOS build --- example/ios/Podfile.lock | 18 +++++++++--------- example/package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 24207675..bd5cafa7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1774,15 +1774,15 @@ SPEC CHECKSUMS: React-CoreModules: 2d68c251bc4080028f2835fa47504e8f20669a21 React-cxxreact: bb0dc212b515d6dba6c6ddc4034584e148857db9 React-debug: fd0ed8ecd5f8a23c7daf5ceaca8aa722a4d083fd - React-defaultsnativemodule: 371dc516e5020f8b87f1d32f8fa6872cafcc2081 - React-domnativemodule: 5d1288b9b8666b818a1004b56a03befc00eb5698 + React-defaultsnativemodule: 0d824306a15dd80e2bea12f4079fbeff9712b301 + React-domnativemodule: 195491d7c1725befd636f84c67bf229203fc7d07 React-Fabric: c12ce848f72cba42fb9e97a73a7c99abc6353f23 React-FabricComponents: 7813d5575c8ea2cda0fef9be4ff9d10987cba512 React-FabricImage: c511a5d612479cb4606edf3557c071956c8735f6 React-featureflags: cf78861db9318ae29982fa8953c92d31b276c9ac - React-featureflagsnativemodule: e774cf495486b0e2a8b324568051d6b4c722fa93 + React-featureflagsnativemodule: 54f6decea27c187c2127e3669a7f5bf2e145e637 React-graphics: 7572851bca7242416b648c45d6af87d93d29281e - React-idlecallbacksnativemodule: d2009bad67ef232a0ee586f53193f37823e81ef1 + React-idlecallbacksnativemodule: 7d21b0e071c3e02bcc897d2c3db51319642dd466 React-ImageManager: aedf54d34d4475c66f4c3da6b8359b95bee904e4 React-jsc: 92ac98e0e03ee54fdaa4ac3936285a4fdb166fab React-jserrorhandler: 0c8949672a00f2a502c767350e591e3ec3d82fb3 @@ -1792,8 +1792,8 @@ SPEC CHECKSUMS: React-jsitracing: 3935b092f85bb1e53b8cf8a00f572413648af46b React-logger: 4072f39df335ca443932e0ccece41fbeb5ca8404 React-Mapbuffer: 714f2fae68edcabfc332b754e9fbaa8cfc68fdd4 - React-microtasksnativemodule: 987cf7e0e0e7129250a48b807e70d3b906c726cf - react-native-bottom-tabs: 894d1fb8fc4e6d525b2da35e83e00e18c420cdf2 + React-microtasksnativemodule: 618b64238e43ef3154079f193aa6649e5320ae19 + react-native-bottom-tabs: 5662b5e3b5968bec6258b9d6f1a0a834bd3f7553 react-native-safe-area-context: 851c62c48dce80ccaa5637b6aa5991a1bc36eca9 React-nativeconfig: 4a9543185905fe41014c06776bf126083795aed9 React-NativeModulesApple: 651670a799672bd54469f2981d91493dda361ddf @@ -1820,11 +1820,11 @@ SPEC CHECKSUMS: React-utils: b2baee839fb869f732d617b97dcfa384b4b4fdb3 ReactCodegen: f177b8fd67788c5c6ff45a39c7482c5f8d77ace6 ReactCommon: 627bd3192ef01a351e804e9709673d3741d38fec - ReactNativeHost: 99c0ffb175cd69de2ac9a70892cd22dac65ea79d + ReactNativeHost: 62249d6e1e42a969159946c035c1cd3f4b1035dd ReactTestApp-DevSupport: b7cd76a3aeee6167f5e14d82f09685059152c426 ReactTestApp-Resources: 7db90c026cccdf40cfa495705ad436ccc4d64154 - RNGestureHandler: 18b9b5d65c77c4744a640f69b7fccdd47ed935c0 - RNScreens: 5288a8dbeedb3c5051aa2d5658c1c553c050b80a + RNGestureHandler: 366823a3ebcc5ddd25550dbfe80e89779c4760b2 + RNScreens: d86f05e9c243a063ca67cda7f4e05d28fe5c31d4 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 4ef80d96a5534f0e01b3055f17d1e19a9fc61b63 diff --git a/example/package.json b/example/package.json index 02ebf8de..6e05fd86 100644 --- a/example/package.json +++ b/example/package.json @@ -5,7 +5,7 @@ "scripts": { "android": "react-native run-android", "build:android": "npm run mkdist && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist && react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", - "build:ios": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist && react-native build-ios --scheme SwiftuiTabviewExample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"", + "build:ios": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist && react-native build-ios --scheme ReactNativeBottomTabs --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"", "build:visionos": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.visionos.jsbundle --assets-dest dist", "ios": "react-native run-ios", "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"", From 140e5e3f16c257dc634b4ef8f9a4c8e428e53ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 3 Oct 2024 20:52:48 +0200 Subject: [PATCH 5/9] fix: support remote images for android --- .../main/java/com/rcttabview/RCTTabView.kt | 8 +++--- src/TabView.tsx | 26 +++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/rcttabview/RCTTabView.kt b/android/src/main/java/com/rcttabview/RCTTabView.kt index 89e21740..df073ee2 100644 --- a/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -1,6 +1,5 @@ package com.rcttabview -import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable @@ -104,9 +103,8 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context var imageSource = ImageSource( context, - source.getString("uri"), - source.getDouble("width"), - source.getDouble("height")) + source.getString("uri") + ) if (Uri.EMPTY == imageSource.uri) { imageSource = getTransparentBitmapImageSource(context) } @@ -119,8 +117,8 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context } } - @SuppressLint("UseCompatLoadingForDrawables") private fun getDrawable(imageSource: ImageSource): Drawable { + // TODO: Check if this can be done using some built-in React Native class val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build() val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context) val result = DataSources.waitForFinalResult(dataSource) as CloseableReference diff --git a/src/TabView.tsx b/src/TabView.tsx index 7b3c07e3..39221fe6 100644 --- a/src/TabView.tsx +++ b/src/TabView.tsx @@ -5,7 +5,7 @@ import { Image, Platform, StyleSheet, View } from 'react-native'; import type { ImageSource } from 'react-native/Libraries/Image/ImageSource'; import TabViewAdapter from './TabViewAdapter'; import useLatestCallback from 'use-latest-callback'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import type { BaseRoute, NavigationState } from './types'; interface Props { @@ -51,16 +51,20 @@ const TabView = ({ badge: route.badge, })); - const icons: ImageSource[] = navigationState.routes - .map((route) => - route.unfocusedIcon - ? route.key === focusedKey - ? route.focusedIcon - : route.unfocusedIcon - : route.focusedIcon - ) - // Pass empty object for icons that are not provided to avoid index mismatch on native side. - .map((icon) => (icon ? Image.resolveAssetSource(icon) : { uri: '' })); + const icons: ImageSource[] = useMemo( + () => + navigationState.routes + .map((route) => + route.unfocusedIcon + ? route.key === focusedKey + ? route.focusedIcon + : route.unfocusedIcon + : route.focusedIcon + ) + // Pass empty object for icons that are not provided to avoid index mismatch on native side. + .map((icon) => (icon ? Image.resolveAssetSource(icon) : { uri: '' })), + [focusedKey, navigationState.routes] + ); const jumpTo = useLatestCallback((key: string) => { const index = navigationState.routes.findIndex( From 8197ef767f520eeb485b55f5978f390a88c3c81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 3 Oct 2024 22:46:41 +0200 Subject: [PATCH 6/9] feat: handle SF Symbols --- example/assets/icons/article.png | Bin 868 -> 0 bytes example/assets/icons/chat.png | Bin 838 -> 0 bytes example/assets/icons/grid.png | Bin 706 -> 0 bytes example/assets/icons/person.png | Bin 1148 -> 0 bytes example/src/App.tsx | 2 + example/src/Examples/FourTabs.tsx | 10 +- example/src/Examples/SFSymbols.tsx | 54 +++++++++ example/src/Examples/ThreeTabs.tsx | 8 +- example/src/Screens/Article.tsx | 178 +++++++++++++++-------------- ios/TabViewImpl.swift | 5 +- ios/TabViewProvider.swift | 6 +- src/TabView.tsx | 69 +++++++---- src/types.ts | 4 +- 13 files changed, 212 insertions(+), 124 deletions(-) delete mode 100644 example/assets/icons/article.png delete mode 100644 example/assets/icons/chat.png delete mode 100644 example/assets/icons/grid.png delete mode 100644 example/assets/icons/person.png create mode 100644 example/src/Examples/SFSymbols.tsx diff --git a/example/assets/icons/article.png b/example/assets/icons/article.png deleted file mode 100644 index 1897117cc1799eddd7109ec0bfad71dac4498e83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 868 zcmV-q1DpJbP)Px&9!W$&RCr$Poqtl>Kn#U#j!*^)r)fA@>B&e>10_R`fIIGwX`42AS)LSa;xGU1 zN-Moj+O?fYJPv$H6&OobXYm05#YF%B02C`wygVHMfMNxTm!|{H5y0b@m-}%XKMcdP zD$Z}1rs31=`^)?4ICiZc+5ta=A3pY$Y|jwF0Q4&UuVUyGLI{A~#qSw{4d8LJS&h@h zLvP#8u6TOu1Rp@B`~JRy=m~-i;OY76Ll0LwtsU44ga809Q)}|Q%6aJVG0I=f{>sSx~ zu;&{07!Uv;ZZfhV0N{YTyi5oHH~>KA%^Cm>1ds&*01W^#AON5dKr{pZya7NY1OU7d zKokT37yxEd_i;M_0AMPxWv;#;id~Hk_(x9wY)RtokQ4c6^EpcG0T2SBpG%Kg~` zPzfr5+;R0RN5!Y?Rea^of>eSk0e4&psNz%hDn0;+U+@4xm5@2EDi{E!z{(r|jX$bW zg0_wW006Bu^Y?srk5u+vblQ6H9iSzuouLXu0aVeKUxumxtH%LQOaK4?*snl#g%s;Q zWaq~jAP57XxCHN;M0KN}s`-?GE zQLW>ug6V)(MH}Y>fXC5|C9^f%ZBb?KMW?Ol06IWd$-Wt?f>niitLIy1=U09inlGWZ z0suVyY-<465_%2*;OS>u1HhKha{vHOKie7rwuGL8lLz3X-@mncpA&E&aGI|$i9tKA zZ?50nIlgQFr}7FbN2Fc05r61v1DJzT`(#G)J@}l)*Sp)RtF;qL2!JJaK}7Y>;6eb* zj+O)=O8ig&OMwt6emH>d5H_3D@OrW8<#o2s{`}i^Tu;;VIpejsVOvm4qRaCFfMVtV u06?(<#mmzH04P?VczHSi0L2OvFaHe$x8r{)PzE~y0000U}o@iaSW-5 zdpkEeNZL@qRdt#`*Y&KVFtJUWELJx$UYueRe#u<9pVwwTsjvELGWET!(kxbn{k2kGJu-wC{*?VV9=^>h zL+HboKUQC-I~)n&|6U#*{@YS0h+%X6PnWDn)}RBIKfjlsQ#Q?O#qZm%^}k=^O86bg z_22g0w_inNlg!sN{QMiQ`r%Z3{gzn^y7-xVj&m3=?s~{kz&Yz9Hr8tK1zaY_<>l6$ z`9azD`%#EKPM-f$pRh?psVW^f^qgJ!Kzhd44ONV*9J0Ug9?as; zdy(`_73jkHAI1)%FPH*8ay4{?zXb{atxdex$H3@u)?sqJBLmYj`@B%&BXb+I=7~=D zqg&0Q(D0sfb?&Lm@aQ#BjSr6pq!&bYxH@c7Wl>1@Co$`cx{a;2g3QMnU`#gXX5{hB z`6F?PH@g0{#Y%<;|96Y|6{Oz0Xz;;4&!t)K?d9-kjQ>9`Z@5)=Z_%56C9jp$d7&|8 zzu7rG?AX^C)SkXptW$k}D?+jH$fT+Vo0-}=tM>2P7bElS)v8DQ&s9R3t5(~J-?x?y z(S3eHZW}A({@h(H91L~ReAW_bVr diff --git a/example/assets/icons/grid.png b/example/assets/icons/grid.png deleted file mode 100644 index d2a41624cb8a8c7965383b1584dd6af76de9e2f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 706 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>V5;$SaSW-5 zdpq0H&&83)dH4B-mL30J3J9e$eC7MMNau8?(5&OzCQ1EyBxP5y|D*A$1GOb3Z2K7# zc5pB-G$gXNwI63>kO)htsE|S9ez?xCr@p4n_uS97U!O*p+)Lq%Y^|-c9tH;^ma(LSR zYK9<1CLPu{?K7@g9}Hq-@aU{!X1K6={To?^phfo>7{0vHu4ZqTvcjB!VehX~_i`C- z$IrU`{P=N;$jOlnRV$elfQB$PtY5gvV-Z7W;vB|_d#ofGPV4qDmk7EGS}6SC7hAe- z)dP`+Q}5@+|28eDYSeUKXyIc%@ZtV_kWasxYX8`1pSOO)+s}^$%8fd?2P=@vTzALiF?V63=FTc@>xIgt*B>bbddF${_T}6AH#XJ12O%w>+H%`b8Tfe z5x(;0R{Wu7HE#>HIxyUrRK>~gVEU)yU4g8J#GXi=>tj1m)c%NJ_p-e#G9TXXeR$QF zv9#f@P?+qccbW_g{_8(wFii1&RmwpE^9QfvD~7%UpB3zZiITz7)z4*}Q$iB}G4m;m diff --git a/example/assets/icons/person.png b/example/assets/icons/person.png deleted file mode 100644 index 001ef6ba7b641b5c97b8d7dd22316c130b195dfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1148 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U@`M_aSW-5 zdpoB#=(d5t(Y0ULQWq4=YMU(T=T;@9!L!%#ao?L67RNfnR;$0Ss`$gm6QTX{?>Fa3 zYg^jlmvOONzIU_Inc0IXeZpURc>JD5jA)>Ptbwk z7?(u(c@71JWY(VD=_~>bF^$i+Z_mxx|JQ0;#`eGO6UFy$&;4!vZ_n?-uL|3jJNvk4LjmuDpXYx&bDTZ#IQ3Eg`cHg^bQ`8$-|z1``T5u9A6xFeJ^TH4 zP0iQn9n1@SErnLz+OU8B?tSlGwNBSBXNZftf4l0@k8YN5h7Fm|W!&Ng?l3rr#AH6H z&^nXHa3a|MM(O9jXY0(Le2y@m^*3#{aq_1i{uqV{Rr`+=FWvU&(b>aiPOvg`e)m2i zA2dPa(A%?9fBv1#@1PXc_rp@aX34@(h6`0$Rbi>~+s-g9ak*5n--e50fhUtj#NKbK zU!9%&T$hVou%PnkYG3>27b}_Kv*LGsv)HYpV;XR4-!_M>Z>Qhe-^{UN&+AB|;6yH= z9qyahL~J5F{R<;oCcExPIXcVBUe#EqF~3vK(BJW;iQxRN&+OxTs`sB;=W(^C`wZie zjmvCI<0dmZD%EYtv}Nu}E?{!__9KwVQET6kur}q@`lXBxliH4zOB|hjzmLy;>G_Ga z75is%bTCh>T6gQJ8}HtV{n`w*x)YWzGUE6aeE#E)*d+@-He@+}m{iqPUB<4kUGBtU z#W%M;yq~y!DTnA`x2E-59$T^YvbSrTnaA~R&ikF-j>XMa8b8b|TK0WA7sKHN${l&H z#2(e`kCvamXWqTPM-%7mpHdpm>JVWURw~IT@mL9(My?B9*!x+s``yW%8yq*jeAxSM z&W+>s{Lhabn_YG+>U`OIwgVqoqks0L?OV_;UM(y8#F#bUvq=w&-}be+JjsO&*A~rZ zzwCbelDf7ym{}B5p+|ke9w(C!(ZbwuAkHX(?Oll#fE0=CLEA%}~mf?`MSL>B+ z{{DKW=5jMEc&BK+wqixu%3oo>mdP@zcnC6_nmVStOqTp00i_>zopr0H5p#GXMYp diff --git a/example/src/App.tsx b/example/src/App.tsx index bfadd950..49dbe840 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -19,10 +19,12 @@ import JSBottomTabs from './Examples/JSBottomTabs'; import ThreeTabs from './Examples/ThreeTabs'; import FourTabs from './Examples/FourTabs'; import MaterialBottomTabs from './Examples/MaterialBottomTabs'; +import SFSymbols from './Examples/SFSymbols'; const examples = [ { component: ThreeTabs, name: 'Three Tabs' }, { component: FourTabs, name: 'Four Tabs' }, + { component: SFSymbols, name: 'SF Symbols' }, { component: FourTabs, name: 'Four Tabs - No header', diff --git a/example/src/Examples/FourTabs.tsx b/example/src/Examples/FourTabs.tsx index 730a4980..aef79b1f 100644 --- a/example/src/Examples/FourTabs.tsx +++ b/example/src/Examples/FourTabs.tsx @@ -11,24 +11,24 @@ export default function FourTabs() { { key: 'article', title: 'Article', - focusedIcon: require('../../assets/icons/article.png'), - unfocusedIcon: require('../../assets/icons/chat.png'), + focusedIcon: require('../../assets/icons/article_dark.png'), + unfocusedIcon: require('../../assets/icons/chat_dark.png'), badge: '!', }, { key: 'albums', title: 'Albums', - focusedIcon: require('../../assets/icons/grid.png'), + focusedIcon: require('../../assets/icons/grid_dark.png'), badge: '5', }, { key: 'contacts', - focusedIcon: require('../../assets/icons/person.png'), + focusedIcon: require('../../assets/icons/person_dark.png'), title: 'Contacts', }, { key: 'chat', - focusedIcon: require('../../assets/icons/chat.png'), + focusedIcon: require('../../assets/icons/chat_dark.png'), title: 'Chat', }, ]); diff --git a/example/src/Examples/SFSymbols.tsx b/example/src/Examples/SFSymbols.tsx new file mode 100644 index 00000000..7333a2a8 --- /dev/null +++ b/example/src/Examples/SFSymbols.tsx @@ -0,0 +1,54 @@ +import TabView, { SceneMap } from 'react-native-bottom-tabs'; +import { useState } from 'react'; +import { Article } from '../Screens/Article'; +import { Albums } from '../Screens/Albums'; +import { Contacts } from '../Screens/Contacts'; +import { Platform } from 'react-native'; + +const isAndroid = Platform.OS === 'android'; + +export default function SFSymbols() { + const [index, setIndex] = useState(0); + const [routes] = useState([ + { + key: 'article', + title: 'Article', + focusedIcon: isAndroid + ? require('../../assets/icons/article_dark.png') + : 'document.fill', + unfocusedIcon: isAndroid + ? require('../../assets/icons/chat_dark.png') + : 'bubble.left.fill', + badge: '!', + }, + { + key: 'albums', + title: 'Albums', + focusedIcon: isAndroid + ? require('../../assets/icons/grid_dark.png') + : 'square.grid.3x2.fill', + badge: '5', + }, + { + key: 'contacts', + focusedIcon: isAndroid + ? require('../../assets/icons/person_dark.png') + : 'person.fill', + title: 'Contacts', + }, + ]); + + const renderScene = SceneMap({ + article: Article, + albums: Albums, + contacts: Contacts, + }); + + return ( + + ); +} diff --git a/example/src/Examples/ThreeTabs.tsx b/example/src/Examples/ThreeTabs.tsx index c71bbfd7..8c867471 100644 --- a/example/src/Examples/ThreeTabs.tsx +++ b/example/src/Examples/ThreeTabs.tsx @@ -10,19 +10,19 @@ export default function ThreeTabs() { { key: 'article', title: 'Article', - focusedIcon: require('../../assets/icons/article.png'), - unfocusedIcon: require('../../assets/icons/chat.png'), + focusedIcon: require('../../assets/icons/article_dark.png'), + unfocusedIcon: require('../../assets/icons/chat_dark.png'), badge: '!', }, { key: 'albums', title: 'Albums', - focusedIcon: require('../../assets/icons/grid.png'), + focusedIcon: require('../../assets/icons/grid_dark.png'), badge: '5', }, { key: 'contacts', - focusedIcon: require('../../assets/icons/person.png'), + focusedIcon: require('../../assets/icons/person_dark.png'), title: 'Contacts', }, ]); diff --git a/example/src/Screens/Article.tsx b/example/src/Screens/Article.tsx index 9e76905c..9accefb2 100644 --- a/example/src/Screens/Article.tsx +++ b/example/src/Screens/Article.tsx @@ -3,6 +3,7 @@ import { Button, Image, Platform, + SafeAreaView, ScrollView, type ScrollViewProps, StyleSheet, @@ -49,95 +50,98 @@ export function Article({ console.log(Platform.OS, ' Rendering Article'); return ( - - + + + + + + {author.name} + {date} + + +