├── .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 |
--------------------------------------------------------------------------------