├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .huskyrc.json ├── .npmignore ├── LICENSE ├── README.md ├── RNMasonryExample ├── .expo-shared │ └── assets.json ├── .gitignore ├── App.tsx ├── app.json ├── assets │ ├── icon.png │ └── splash.png ├── babel.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── assets ├── horizontal.gif └── vertical.gif ├── babel.config.js ├── config.yml ├── jest.config.js ├── package.json ├── precommit-lint.sh ├── src ├── __tests__ │ ├── __snapshots__ │ │ └── index.snapshot.test.tsx.snap │ ├── index.snapshot.test.tsx │ └── index.unit.test.tsx └── index.tsx ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | plugins: ["tsc", "jest"], 4 | root: true, 5 | extends: "@react-native-community", 6 | rules: { 7 | quotes: ["warn", "double", { allowTemplateLiterals: true }], 8 | "tsc/config": [ 9 | 1, 10 | { 11 | configFile: "tsconfig.json" 12 | } 13 | ], 14 | "comma-dangle": 0, 15 | "react/prop-types": 1, 16 | "prettier/prettier": 1, 17 | "@typescript-eslint/no-unused-vars": 1 18 | }, 19 | env: { 20 | "jest/globals": true 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory 2 | lib/ 3 | 4 | # Created by https://www.gitignore.io/api/code,macos,webstorm,reactnative 5 | # Edit at https://www.gitignore.io/?templates=code,macos,webstorm,reactnative 6 | 7 | ### Code ### 8 | .vscode/* 9 | !.vscode/settings.json 10 | !.vscode/tasks.json 11 | !.vscode/launch.json 12 | !.vscode/extensions.json 13 | 14 | ### macOS ### 15 | # General 16 | .DS_Store 17 | .AppleDouble 18 | .LSOverride 19 | 20 | # Icon must end with two \r 21 | Icon 22 | 23 | # Thumbnails 24 | ._* 25 | 26 | # Files that might appear in the root of a volume 27 | .DocumentRevisions-V100 28 | .fseventsd 29 | .Spotlight-V100 30 | .TemporaryItems 31 | .Trashes 32 | .VolumeIcon.icns 33 | .com.apple.timemachine.donotpresent 34 | 35 | # Directories potentially created on remote AFP share 36 | .AppleDB 37 | .AppleDesktop 38 | Network Trash Folder 39 | Temporary Items 40 | .apdisk 41 | 42 | ### ReactNative ### 43 | # React Native Stack Base 44 | 45 | .expo 46 | __generated__ 47 | 48 | ### ReactNative.Linux Stack ### 49 | *~ 50 | 51 | # temporary files which can be created if a process still has a handle open of a deleted file 52 | .fuse_hidden* 53 | 54 | # KDE directory preferences 55 | .directory 56 | 57 | # Linux trash folder which might appear on any partition or disk 58 | .Trash-* 59 | 60 | # .nfs files are created when an open file is removed but is still being accessed 61 | .nfs* 62 | 63 | ### ReactNative.macOS Stack ### 64 | # General 65 | 66 | # Icon must end with two \r 67 | Icon 68 | 69 | 70 | # Thumbnails 71 | 72 | # Files that might appear in the root of a volume 73 | 74 | # Directories potentially created on remote AFP share 75 | 76 | ### ReactNative.Android Stack ### 77 | # Built application files 78 | *.apk 79 | *.ap_ 80 | *.aab 81 | 82 | # Files for the ART/Dalvik VM 83 | *.dex 84 | 85 | # Java class files 86 | *.class 87 | 88 | # Generated files 89 | bin/ 90 | gen/ 91 | out/ 92 | release/ 93 | 94 | # Gradle files 95 | .gradle/ 96 | build/ 97 | 98 | # Local configuration file (sdk path, etc) 99 | local.properties 100 | 101 | # Proguard folder generated by Eclipse 102 | proguard/ 103 | 104 | # Log Files 105 | *.log 106 | 107 | # Android Studio Navigation editor temp files 108 | .navigation/ 109 | 110 | # Android Studio captures folder 111 | captures/ 112 | 113 | # IntelliJ 114 | *.iml 115 | .idea/workspace.xml 116 | .idea/tasks.xml 117 | .idea/gradle.xml 118 | .idea/assetWizardSettings.xml 119 | .idea/dictionaries 120 | .idea/libraries 121 | # Android Studio 3 in .gitignore file. 122 | .idea/caches 123 | .idea/modules.xml 124 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 125 | .idea/navEditor.xml 126 | 127 | # Keystore files 128 | # Uncomment the following lines if you do not want to check your keystore files in. 129 | #*.jks 130 | #*.keystore 131 | 132 | # External native build folder generated in Android Studio 2.2 and later 133 | .externalNativeBuild 134 | 135 | # Google Services (e.g. APIs or Firebase) 136 | # google-services.json 137 | 138 | # Freeline 139 | freeline.py 140 | freeline/ 141 | freeline_project_description.json 142 | 143 | # fastlane 144 | fastlane/report.xml 145 | fastlane/Preview.html 146 | fastlane/screenshots 147 | fastlane/test_output 148 | fastlane/readme.md 149 | 150 | # Version control 151 | vcs.xml 152 | 153 | # lint 154 | lint/intermediates/ 155 | lint/generated/ 156 | lint/outputs/ 157 | lint/tmp/ 158 | # lint/reports/ 159 | 160 | ### ReactNative.Buck Stack ### 161 | buck-out/ 162 | .buckconfig.local 163 | .buckd/ 164 | .buckversion 165 | .fakebuckversion 166 | 167 | ### ReactNative.Gradle Stack ### 168 | .gradle 169 | 170 | # Ignore Gradle GUI config 171 | gradle-app.setting 172 | 173 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 174 | !gradle-wrapper.jar 175 | 176 | # Cache of project 177 | .gradletasknamecache 178 | 179 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 180 | # gradle/wrapper/gradle-wrapper.properties 181 | 182 | ### ReactNative.Xcode Stack ### 183 | # Xcode 184 | # 185 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 186 | 187 | ## User settings 188 | xcuserdata/ 189 | 190 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 191 | *.xcscmblueprint 192 | *.xccheckout 193 | 194 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 195 | DerivedData/ 196 | *.moved-aside 197 | *.pbxuser 198 | !default.pbxuser 199 | *.mode1v3 200 | !default.mode1v3 201 | *.mode2v3 202 | !default.mode2v3 203 | *.perspectivev3 204 | !default.perspectivev3 205 | 206 | ## Xcode Patch 207 | *.xcodeproj/* 208 | !*.xcodeproj/project.pbxproj 209 | !*.xcodeproj/xcshareddata/ 210 | !*.xcworkspace/contents.xcworkspacedata 211 | /*.gcno 212 | 213 | ### ReactNative.Node Stack ### 214 | # Logs 215 | logs 216 | npm-debug.log* 217 | yarn-debug.log* 218 | yarn-error.log* 219 | lerna-debug.log* 220 | 221 | # Diagnostic reports (https://nodejs.org/api/report.html) 222 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 223 | 224 | # Runtime data 225 | pids 226 | *.pid 227 | *.seed 228 | *.pid.lock 229 | 230 | # Directory for instrumented libs generated by jscoverage/JSCover 231 | lib-cov 232 | 233 | # Coverage directory used by tools like istanbul 234 | coverage 235 | *.lcov 236 | 237 | # nyc test coverage 238 | .nyc_output 239 | 240 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 241 | .grunt 242 | 243 | # Bower dependency directory (https://bower.io/) 244 | bower_components 245 | 246 | # node-waf configuration 247 | .lock-wscript 248 | 249 | # Compiled binary addons (https://nodejs.org/api/addons.html) 250 | build/Release 251 | 252 | # Dependency directories 253 | node_modules/ 254 | jspm_packages/ 255 | 256 | # TypeScript v1 declaration files 257 | typings/ 258 | 259 | # TypeScript cache 260 | *.tsbuildinfo 261 | 262 | # Optional npm cache directory 263 | .npm 264 | 265 | # Optional eslint cache 266 | .eslintcache 267 | 268 | # Optional REPL history 269 | .node_repl_history 270 | 271 | # Output of 'npm pack' 272 | *.tgz 273 | 274 | # Yarn Integrity file 275 | .yarn-integrity 276 | 277 | # dotenv environment variables file 278 | .env 279 | .env.test 280 | 281 | # parcel-bundler cache (https://parceljs.org/) 282 | .cache 283 | 284 | # next.js build output 285 | .next 286 | 287 | # nuxt.js build output 288 | .nuxt 289 | 290 | # rollup.js default build output 291 | dist/ 292 | 293 | # Uncomment the public line if your project uses Gatsby 294 | # https://nextjs.org/blog/next-9-1#public-directory-support 295 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 296 | # public 297 | 298 | # Storybook build outputs 299 | .out 300 | .storybook-out 301 | 302 | # vuepress build output 303 | .vuepress/dist 304 | 305 | # Serverless directories 306 | .serverless/ 307 | 308 | # FuseBox cache 309 | .fusebox/ 310 | 311 | # DynamoDB Local files 312 | .dynamodb/ 313 | 314 | # Temporary folders 315 | tmp/ 316 | temp/ 317 | 318 | ### WebStorm ### 319 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 320 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 321 | 322 | # User-specific stuff 323 | .idea/**/workspace.xml 324 | .idea/**/tasks.xml 325 | .idea/**/usage.statistics.xml 326 | .idea/**/dictionaries 327 | .idea/**/shelf 328 | 329 | # Generated files 330 | .idea/**/contentModel.xml 331 | 332 | # Sensitive or high-churn files 333 | .idea/**/dataSources/ 334 | .idea/**/dataSources.ids 335 | .idea/**/dataSources.local.xml 336 | .idea/**/sqlDataSources.xml 337 | .idea/**/dynamic.xml 338 | .idea/**/uiDesigner.xml 339 | .idea/**/dbnavigator.xml 340 | 341 | # Gradle 342 | .idea/**/gradle.xml 343 | .idea/**/libraries 344 | 345 | # Gradle and Maven with auto-import 346 | # When using Gradle or Maven with auto-import, you should exclude module files, 347 | # since they will be recreated, and may cause churn. Uncomment if using 348 | # auto-import. 349 | # .idea/modules.xml 350 | # .idea/*.iml 351 | # .idea/modules 352 | # *.iml 353 | # *.ipr 354 | 355 | # CMake 356 | cmake-build-*/ 357 | 358 | # Mongo Explorer plugin 359 | .idea/**/mongoSettings.xml 360 | 361 | # File-based project format 362 | *.iws 363 | 364 | # IntelliJ 365 | 366 | # mpeltonen/sbt-idea plugin 367 | .idea_modules/ 368 | 369 | # JIRA plugin 370 | atlassian-ide-plugin.xml 371 | 372 | # Cursive Clojure plugin 373 | .idea/replstate.xml 374 | 375 | # Crashlytics plugin (for Android Studio and IntelliJ) 376 | com_crashlytics_export_strings.xml 377 | crashlytics.properties 378 | crashlytics-build.properties 379 | fabric.properties 380 | 381 | # Editor-based Rest Client 382 | .idea/httpRequests 383 | 384 | # Android studio 3.1+ serialized cache file 385 | .idea/caches/build_file_checksums.ser 386 | 387 | ### WebStorm Patch ### 388 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 389 | 390 | # *.iml 391 | # modules.xml 392 | # .idea/misc.xml 393 | # *.ipr 394 | 395 | # Sonarlint plugin 396 | .idea/**/sonarlint/ 397 | 398 | # SonarQube Plugin 399 | .idea/**/sonarIssues.xml 400 | 401 | # Markdown Navigator plugin 402 | .idea/**/markdown-navigator.xml 403 | .idea/**/markdown-navigator/ 404 | 405 | # End of https://www.gitignore.io/api/code,macos,webstorm,reactnative 406 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "pretty-quick --staged && sh precommit-lint.sh" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | RNMasonryExample 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present DaniAkash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 🧱 React Native Masonry ScrollView 3 |

4 | 5 |
6 | 7 | Simple easy to use Masonry ScrollView for React Native that extends the original ScrollView ✨ 8 | 9 | [![Version][version-badge]][package] 10 | [![Downloads][downloads-badge]][npmtrends] 11 | 12 | 14 | 15 | [![Star on GitHub][github-star-badge]][github-star] 16 | [![Watch on GitHub][github-watch-badge]][github-watch] 17 | [![Twitter Follow][twitter-badge]][twitter] 18 | 19 |
20 | 21 | ## Installation 22 | 23 | ```sh 24 | yarn add react-native-masonry-scrollview 25 | 26 | # or 27 | 28 | npm i react-native-masonry-scrollview 29 | ``` 30 | 31 | ## Usage 32 | 33 | The Masonry ScrollView splits the content of the `ScrollView` into multiple columns or rows (depending on horizontal or vertical scroll) and renders the items into the individual column's `View` component. This component is built to extend the existing `ScrollView` component hence all the properties of the `ScrollView` will work with it and it can render any component supplied to it as children. 34 | 35 | ```jsx 36 | import React from "react"; 37 | import { View, StyleSheet } from "react-native"; 38 | import RNMasonryScroll from "react-native-masonry-scrollview"; 39 | 40 | const Box = () => ; 41 | 42 | const App = () => ( 43 | 44 | {/** 45 | * Masonry ScrollView only expects children as a list 46 | */} 47 | {[ 48 | , 49 | , 50 | , 51 | , 52 | , 53 | 54 | ]} 55 | 56 | ); 57 | 58 | const styles = StyleSheet.create({ 59 | box: { 60 | height: 50, 61 | width: 50, 62 | backgroundColor: "red", 63 | margin: 16 64 | } 65 | }); 66 | 67 | export default App; 68 | ``` 69 | 70 | ## Advanced Usage 71 | 72 | Refer the example expo app in `RNMasonryExample/` directory of this repo 👍 73 | 74 | ## Properties 75 | 76 | ### `children: ReactNode[]` 77 | 78 | Children of the Masonry ScrollView component should always be an array of React Nodes. 79 | 80 | --- 81 | 82 | ### `columns?: number` 83 | 84 | Number of columns to split the Masonry 85 | 86 | --- 87 | 88 | ### `columnStyle?: StyleProp` 89 | 90 | Style applied to the `View` component that is wrapping your components inside the Masonry ScrollView. 91 | 92 | --- 93 | 94 | ### `oddColumnStyle?: StyleProp` 95 | 96 | Style applied only to the n-th odd columns of the Masonry ScrollView. If you have 3 columns, this style will be applied to columns 1 & 3. 97 | 98 | --- 99 | 100 | ### `evenColumnStyle?: StyleProp` 101 | 102 | Style applied only to the n-th even columns of the Masonry ScrollView. If you have 3 columns, this style will be applied to column 2. 103 | 104 | --- 105 | 106 | ### `horizontal?: boolean` 107 | 108 | Control if the masonry is horizontal or vertical 109 | 110 | --- 111 | 112 | ### ScrollViewProps 113 | 114 | All the existing ScrollView Props are supported by this component since it simply extends the actual ScrollView. 115 | 116 | --- 117 | 118 | ## Example App 119 | 120 | The example app is built with expo, you can run the app following the official [expo docs](https://expo.io/learn). 121 | 122 |

123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 | From the Example App 131 |

132 | 133 | ## Licenses 134 | 135 | MIT © [DaniAkash][twitter] 136 | 137 | 141 | 142 | [downloads-badge]: https://img.shields.io/npm/dm/react-native-masonry-scrollview.svg?style=flat-square 143 | [npmtrends]: http://www.npmtrends.com/react-native-masonry-scrollview 144 | [package]: https://www.npmjs.com/package/react-native-masonry-scrollview 145 | [version-badge]: https://img.shields.io/npm/v/react-native-masonry-scrollview.svg?style=flat-square 146 | [twitter]: https://twitter.com/dani_akash_ 147 | [twitter-badge]: https://img.shields.io/twitter/follow/dani_akash_?style=social 148 | [github-watch-badge]: https://img.shields.io/github/watchers/DaniAkash/react-native-masonry-scrollview.svg?style=social 149 | [github-watch]: https://github.com/DaniAkash/react-native-masonry-scrollview/watchers 150 | [github-star-badge]: https://img.shields.io/github/stars/DaniAkash/react-native-masonry-scrollview.svg?style=social 151 | [github-star]: https://github.com/DaniAkash/react-native-masonry-scrollview/stargazers 152 | -------------------------------------------------------------------------------- /RNMasonryExample/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } 5 | -------------------------------------------------------------------------------- /RNMasonryExample/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /RNMasonryExample/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { StyleSheet, Text, View, SafeAreaView, Switch } from "react-native"; 3 | import * as Animatable from "react-native-animatable"; 4 | import RNMasonryScroll from "react-native-masonry-scrollview"; 5 | import Image from "react-native-scalable-image"; 6 | import { useResponsiveWidth } from "react-native-responsive-dimensions"; 7 | 8 | const { createAnimatableComponent } = Animatable; 9 | 10 | const AnimatableView = createAnimatableComponent(View); 11 | 12 | const images = [ 13 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 14 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 15 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 16 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 17 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 18 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 19 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 20 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 21 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 22 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 23 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 24 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 25 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 26 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 27 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 28 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 29 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 30 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 31 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 32 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 33 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 34 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 35 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 36 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 37 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 38 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 39 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 40 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 41 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 42 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 43 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 44 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 45 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 46 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 47 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 48 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 49 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 50 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 51 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 52 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 53 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 54 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 55 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 56 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 57 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 58 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 59 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 60 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 61 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 62 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 63 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 64 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 65 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 66 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 67 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 68 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 69 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 70 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 71 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 72 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 73 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 74 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 75 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 76 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 77 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 78 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 79 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 80 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 81 | "https://images.unsplash.com/photo-1558981001-792f6c0d5068?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 82 | "https://images.unsplash.com/photo-1580502734537-c6a7ee0bdb41?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 83 | "https://images.unsplash.com/photo-1580500325788-5012abe74ebf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", 84 | "https://images.unsplash.com/photo-1580524764764-284c2a54b185?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60" 85 | ]; 86 | 87 | const App = () => { 88 | const imageWidth: number = useResponsiveWidth(50) - 20; 89 | const [isHorizontal, setIsHorizontal] = useState(false); 90 | 91 | const toggleHorizontal = () => setIsHorizontal(!isHorizontal); 92 | 93 | const imageProp = isHorizontal 94 | ? { height: imageWidth } 95 | : { width: imageWidth }; 96 | 97 | return ( 98 | 99 | 100 | 101 | Horizontal 102 | 103 | 114 | {images.map((image, imageIndex) => { 115 | return ( 116 | 121 | 122 | 123 | ); 124 | })} 125 | 126 | 127 | ); 128 | }; 129 | 130 | const styles = StyleSheet.create({ 131 | header: { 132 | flexDirection: "row", 133 | alignItems: "center", 134 | justifyContent: "center", 135 | margin: 8 136 | }, 137 | headerText: { 138 | fontWeight: "bold", 139 | marginHorizontal: 8, 140 | fontSize: 16 141 | }, 142 | imageContainer: { 143 | margin: 10, 144 | borderRadius: 10, 145 | overflow: "hidden", 146 | backgroundColor: "silver" 147 | }, 148 | evenColumnStyle: {}, 149 | oddColumnStyleVertical: { marginTop: 60 }, 150 | oddColumnStyleHorizontal: { marginLeft: 60 } 151 | }); 152 | 153 | export default App; 154 | -------------------------------------------------------------------------------- /RNMasonryExample/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "RNMasonryExample", 4 | "slug": "RNMasonryExample", 5 | "privacy": "public", 6 | "sdkVersion": "36.0.0", 7 | "platforms": ["ios", "android", "web"], 8 | "version": "1.0.0", 9 | "orientation": "portrait", 10 | "icon": "./assets/icon.png", 11 | "splash": { 12 | "image": "./assets/splash.png", 13 | "resizeMode": "contain", 14 | "backgroundColor": "#ffffff" 15 | }, 16 | "updates": { 17 | "fallbackToCacheTimeout": 0 18 | }, 19 | "assetBundlePatterns": ["**/*"], 20 | "ios": { 21 | "supportsTablet": true 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RNMasonryExample/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/react-native-toolkit/react-native-masonry-scrollview/8ecc028eb2b78a84959a17a2551d101c9d6af235/RNMasonryExample/assets/icon.png -------------------------------------------------------------------------------- /RNMasonryExample/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/react-native-toolkit/react-native-masonry-scrollview/8ecc028eb2b78a84959a17a2551d101c9d6af235/RNMasonryExample/assets/splash.png -------------------------------------------------------------------------------- /RNMasonryExample/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"] 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /RNMasonryExample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "expo": "~36.0.0", 12 | "react": "~16.9.0", 13 | "react-dom": "~16.9.0", 14 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz", 15 | "react-native-animatable": "^1.3.3", 16 | "react-native-masonry-scrollview": "^0.0.1", 17 | "react-native-responsive-dimensions": "^3.0.0", 18 | "react-native-scalable-image": "^1.0.0", 19 | "react-native-screens": "2.0.0-alpha.12", 20 | "react-native-web": "~0.11.7" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.0.0", 24 | "@types/react": "~16.9.0", 25 | "@types/react-native": "~0.60.23", 26 | "babel-preset-expo": "~8.0.0", 27 | "typescript": "~3.6.3" 28 | }, 29 | "private": true 30 | } 31 | -------------------------------------------------------------------------------- /RNMasonryExample/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/horizontal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/react-native-toolkit/react-native-masonry-scrollview/8ecc028eb2b78a84959a17a2551d101c9d6af235/assets/horizontal.gif -------------------------------------------------------------------------------- /assets/vertical.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/react-native-toolkit/react-native-masonry-scrollview/8ecc028eb2b78a84959a17a2551d101c9d6af235/assets/vertical.gif -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference 2 | version: 2.1 3 | # Use a package of configuration called an orb. 4 | orbs: 5 | # Declare a dependency on the welcome-orb 6 | welcome: circleci/welcome-orb@0.4.1 7 | # Orchestrate or schedule a set of jobs 8 | workflows: 9 | # Name the workflow "welcome" 10 | welcome: 11 | # Run the welcome/run job in its own container 12 | jobs: 13 | - welcome/run 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "@testing-library/react-native", 3 | verbose: true, 4 | transformIgnorePatterns: [ 5 | "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base)" 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-masonry-scrollview", 3 | "version": "0.0.2", 4 | "description": "A React Native Masonry ScrollView that extends the inbuilt ScrollView component", 5 | "scripts": { 6 | "build": "rm -rf ./lib && tsc -p .", 7 | "test": "jest" 8 | }, 9 | "main": "lib/index.js", 10 | "author": "DaniAkash (https://github.com/DaniAkash)", 11 | "repository": "DaniAkash/react-native-masonry-scrollview", 12 | "license": "MIT", 13 | "keywords": [ 14 | "react", 15 | "react-native", 16 | "masonry", 17 | "scrollview", 18 | "masonry-scroll" 19 | ], 20 | "devDependencies": { 21 | "@react-native-community/eslint-config": "^0.0.7", 22 | "@testing-library/react-native": "^5.0.3", 23 | "@types/jest": "^25.1.1", 24 | "@types/react": "^16.9.19", 25 | "@types/react-native": "^0.61.7", 26 | "@types/react-test-renderer": "^16.9.2", 27 | "eslint": "^6.8.0", 28 | "eslint-plugin-prettier": "^3.1.2", 29 | "eslint-plugin-tsc": "^1.2.0", 30 | "husky": "^4.2.1", 31 | "jest": "^25.1.0", 32 | "metro-react-native-babel-preset": "^0.58.0", 33 | "prettier": "^1.19.1", 34 | "pretty-quick": "^2.0.1", 35 | "react": "^16.12.0", 36 | "react-native": "^0.61.5", 37 | "react-test-renderer": "^16.12.0", 38 | "typescript": "^3.7.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /precommit-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "ESLint running for staged files..." 4 | 5 | # from https://eslint.org/docs/user-guide/integrations#source-control - Git pre-commit hook that only lints staged changes 6 | for file in $(git diff --cached --name-only | grep -E '\.(js|jsx|ts|tsx)$') 7 | do 8 | git show ":$file" | node_modules/.bin/eslint --stdin --stdin-filename "$file" --max-warnings 0 # we only want to lint the staged changes, not any un-staged changes 9 | if [ $? -ne 0 ]; then 10 | echo "ESLint failed on staged file '$file'. Please check your code and try again. You can run ESLint manually via npm run eslint." 11 | exit 1 # exit with failure status 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/index.snapshot.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Renders Horizontal Masonry with 2 columns 1`] = ` 4 | 12 | 23 | 24 | Text 1 25 | 26 | 27 | 38 | 39 | Text 2 40 | 41 | 42 | 43 | `; 44 | 45 | exports[`Renders Horizontal Masonry with 3 columns 1`] = ` 46 | 54 | 65 | 66 | Text 1 67 | 68 | 69 | Text 3 70 | 71 | 72 | 83 | 84 | Text 2 85 | 86 | 87 | 88 | `; 89 | 90 | exports[`Renders Vertical Masonry with 2 columns 1`] = ` 91 | 98 | 109 | 110 | Text 1 111 | 112 | 113 | 124 | 125 | Text 2 126 | 127 | 128 | 129 | `; 130 | 131 | exports[`Renders Vertical Masonry with 3 columns 1`] = ` 132 | 139 | 150 | 151 | Text 1 152 | 153 | 154 | Text 3 155 | 156 | 157 | 168 | 169 | Text 2 170 | 171 | 172 | 173 | `; 174 | -------------------------------------------------------------------------------- /src/__tests__/index.snapshot.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from "react"; 2 | import renderer from "react-test-renderer"; 3 | import RNMasonryScrollView from "../index"; 4 | import { Text } from "react-native"; 5 | 6 | const column1Text = "Text 1"; 7 | const column2Text = "Text 2"; 8 | const column3Text = "Text 3"; 9 | 10 | const VMasonryComponentTwoColumns = () => { 11 | return ( 12 | 13 | {[{column1Text}, {column2Text}]} 14 | 15 | ); 16 | }; 17 | 18 | const testSnapShot = (Component: ReactElement) => { 19 | const tree = renderer.create(Component).toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }; 22 | 23 | it("Renders Vertical Masonry with 2 columns", () => { 24 | testSnapShot(); 25 | }); 26 | 27 | const VMasonryComponentThreeColumns = () => { 28 | return ( 29 | 30 | {[ 31 | {column1Text}, 32 | {column2Text}, 33 | {column3Text} 34 | ]} 35 | 36 | ); 37 | }; 38 | 39 | it("Renders Vertical Masonry with 3 columns", () => { 40 | testSnapShot(); 41 | }); 42 | 43 | const HMasonryComponentTwoColumns = () => { 44 | return ( 45 | 46 | {[{column1Text}, {column2Text}]} 47 | 48 | ); 49 | }; 50 | 51 | it("Renders Horizontal Masonry with 2 columns", () => { 52 | testSnapShot(); 53 | }); 54 | 55 | const HMasonryComponentThreeColumns = () => { 56 | return ( 57 | 58 | {[ 59 | {column1Text}, 60 | {column2Text}, 61 | {column3Text} 62 | ]} 63 | 64 | ); 65 | }; 66 | 67 | it("Renders Horizontal Masonry with 3 columns", () => { 68 | testSnapShot(); 69 | }); 70 | -------------------------------------------------------------------------------- /src/__tests__/index.unit.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text } from "react-native"; 3 | import { render } from "@testing-library/react-native"; 4 | import RNMasonryScrollView, { generateMasonryGrid } from "../index"; 5 | 6 | const column1Text = "Text 1"; 7 | const column2Text = "Text 2"; 8 | 9 | const MasonryTestComponent = ( 10 | { 11 | isHorizontal = true 12 | }: { 13 | isHorizontal?: boolean; 14 | } = { isHorizontal: true } 15 | ) => { 16 | return ( 17 | 18 | {[{column1Text}, {column2Text}]} 19 | 20 | ); 21 | }; 22 | 23 | it("Renders Vertical Masonry", () => { 24 | const { queryByText } = render(); 25 | 26 | const text1 = queryByText(column1Text); 27 | expect(text1).toBeTruthy(); 28 | const text2 = queryByText(column2Text); 29 | expect(text2).toBeTruthy(); 30 | }); 31 | 32 | it("Renders Horizontal Masonry", () => { 33 | const { queryByText } = render(); 34 | 35 | const text1 = queryByText(column1Text); 36 | expect(text1).toBeTruthy(); 37 | const text2 = queryByText(column2Text); 38 | expect(text2).toBeTruthy(); 39 | }); 40 | 41 | it("Masonry gets generated properly", () => { 42 | const masonryArray = generateMasonryGrid([1, 2, 3, 4], 2); 43 | expect(masonryArray[0]).toStrictEqual([1, 3]); 44 | expect(masonryArray[1]).toStrictEqual([2, 4]); 45 | 46 | const masonryArray2 = generateMasonryGrid([], 2); 47 | expect(masonryArray2).toStrictEqual([]); 48 | 49 | const masonryArray3 = generateMasonryGrid([1, 2, 3, 4, 5, 6, 7], 3); 50 | expect(masonryArray3[0]).toStrictEqual([1, 4, 7]); 51 | expect(masonryArray3[1]).toStrictEqual([2, 5]); 52 | expect(masonryArray3[2]).toStrictEqual([3, 6]); 53 | }); 54 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { 3 | ScrollView, 4 | ScrollViewProps, 5 | View, 6 | StyleProp, 7 | ViewStyle, 8 | StyleSheet 9 | } from "react-native"; 10 | 11 | export interface RNMasonryScrollViewProps extends ScrollViewProps { 12 | children: ReactNode[]; 13 | columns?: number; 14 | columnStyle?: StyleProp; 15 | oddColumnStyle?: StyleProp; 16 | evenColumnStyle?: StyleProp; 17 | } 18 | 19 | export function generateMasonryGrid(data: T[], columns: number): T[][] { 20 | return data.reduce((collection: T[][], child: T, childIndex: number) => { 21 | const itemIndex = childIndex % columns; 22 | if (collection[itemIndex]) { 23 | collection[itemIndex].push(child); 24 | } else { 25 | collection[itemIndex] = []; 26 | collection[itemIndex].push(child); 27 | } 28 | return collection; 29 | }, []); 30 | } 31 | 32 | const RNMasonryScrollView = ({ 33 | children = [], 34 | columns = 2, 35 | columnStyle = null, 36 | oddColumnStyle = null, 37 | evenColumnStyle = null, 38 | horizontal, 39 | ...otherProps 40 | }: RNMasonryScrollViewProps) => { 41 | const masonryGrid = generateMasonryGrid(children, columns); 42 | 43 | return ( 44 | 51 | {masonryGrid.map((column, columnIndex) => { 52 | return ( 53 | 63 | {column.map(item => item)} 64 | 65 | ); 66 | })} 67 | 68 | ); 69 | }; 70 | 71 | const styles = StyleSheet.create({ 72 | verticalColumnStyle: { flexDirection: "row" }, 73 | horizontalColumnStyle: { flexDirection: "column" } 74 | }); 75 | 76 | export default RNMasonryScrollView; 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 6 | "module": "ES6" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 11 | "declaration": true /* Generates corresponding '.d.ts' file. */, 12 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./lib" /* Redirect output structure to the directory. */, 16 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "Node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | "types": [ 48 | "react", 49 | "react-native", 50 | "jest" 51 | ] /* Type declaration files to be included in compilation. */, 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "exclude": ["node_modules", "RNMasonryExample", "lib"] 71 | } 72 | --------------------------------------------------------------------------------