├── .eslintrc ├── .gitignore ├── .storybook └── config.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── demo ├── favicon.png ├── index.html └── src │ ├── assets │ ├── DSC_8122_1024x1024.jpeg │ ├── github.svg │ └── twitter.svg │ ├── components │ ├── App │ │ ├── App.js │ │ ├── App.scss │ │ └── index.js │ ├── ComponentName │ │ ├── ComponentName.js │ │ ├── ComponentName.scss │ │ └── index.js │ ├── FiddleEmbed │ │ ├── FiddleEmbed.js │ │ ├── FiddleEmbed.scss │ │ └── index.js │ ├── Footer │ │ ├── Footer.js │ │ ├── Footer.scss │ │ └── index.js │ ├── Header │ │ ├── Header.js │ │ ├── Header.scss │ │ └── index.js │ ├── HorizontalRule │ │ ├── HorizontalRule.js │ │ ├── HorizontalRule.scss │ │ └── index.js │ ├── MaxWidthWrapper │ │ ├── MaxWidthWrapper.js │ │ ├── MaxWidthWrapper.scss │ │ └── index.js │ └── Nav │ │ ├── Nav.js │ │ ├── Nav.scss │ │ └── index.js │ ├── index.js │ └── style-variables.scss ├── documentation ├── comparators.md ├── markup.md └── predicates.md ├── nwb.config.js ├── package.json ├── src ├── components │ ├── BaseCollectionHelper │ │ ├── BaseCollectionHelper.js │ │ ├── BaseCollectionHelper.test.js │ │ └── index.js │ ├── Every │ │ ├── Every.js │ │ ├── Every.stories.js │ │ ├── Every.test.js │ │ └── index.js │ ├── Filter │ │ ├── Filter.js │ │ ├── Filter.stories.js │ │ ├── Filter.test.js │ │ └── index.js │ ├── Find │ │ ├── Find.js │ │ ├── Find.stories.js │ │ ├── Find.test.js │ │ └── index.js │ ├── First │ │ ├── First.js │ │ ├── First.stories.js │ │ ├── First.test.js │ │ └── index.js │ ├── Last │ │ ├── Last.js │ │ ├── Last.stories.js │ │ ├── Last.test.js │ │ └── index.js │ ├── Map │ │ ├── Map.js │ │ ├── Map.stories.js │ │ ├── Map.test.js │ │ └── index.js │ ├── Reject │ │ ├── Reject.js │ │ ├── Reject.stories.js │ │ ├── Reject.test.js │ │ └── index.js │ ├── Reverse │ │ ├── Reverse.js │ │ ├── Reverse.stories.js │ │ ├── Reverse.test.js │ │ └── index.js │ ├── Some │ │ ├── Some.js │ │ ├── Some.stories.js │ │ ├── Some.test.js │ │ └── index.js │ └── Sort │ │ ├── Sort.js │ │ ├── Sort.stories.js │ │ ├── Sort.test.js │ │ └── index.js ├── constants │ └── index.js ├── helpers │ ├── README.md │ ├── error-message.helpers.js │ ├── misc.helpers.js │ ├── misc.helpers.test.js │ ├── story │ │ └── FilterController.js │ └── test.helpers.js ├── index.js └── utils │ ├── README.md │ ├── apply-predicate-to-collection-with.js │ ├── filter-by.js │ ├── filter-by.test.js │ ├── find-by.js │ ├── find-by.test.js │ ├── reject-by.js │ ├── reject-by.test.js │ ├── sort-by.js │ └── sort-by.test.js ├── test ├── README.md └── composition.test.js ├── tools ├── add-component.js ├── performance-checks.css └── performance-checks.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "no-unused-expressions": 0, 5 | "import/no-extraneous-dependencies": 0, 6 | "import/prefer-default-export": 0, 7 | "react/require-extension": 0, 8 | "react/jsx-filename-extension": 0, 9 | "react/forbid-prop-types": 0, 10 | "no-console": [1, { allow: ["warn", "error", "info"] }], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | /storybook-static 8 | npm-debug.log* 9 | 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@kadira/storybook'; 2 | 3 | const components = require.context('../src/components', true, /.stories.js$/); 4 | 5 | function loadStories() { 6 | components.keys().forEach(filename => components(filename)); 7 | 8 | // Also learn our performance checks, which run in Storybook. 9 | require('../tools/performance-checks'); 10 | } 11 | 12 | configure(loadStories, module); 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 4 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | before_install: 12 | - npm install codecov.io coveralls 13 | 14 | after_success: 15 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 16 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 17 | 18 | branches: 19 | only: 20 | - master 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the components's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Joshua Comeau. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Collection Helpers 2 | #### A suite of composable utility components to manipulate collections. 3 | 4 | [![Travis][build-badge]][build] 5 | [![npm package][npm-badge]][npm] 6 | [![Coveralls][coveralls-badge]][coveralls] 7 | 8 | 9 | 10 | ## Quick Example 11 | 12 | ```jsx 13 | import { Filter, Sort, First } from 'react-collection-helpers'; 14 | 15 | const users = [ 16 | { id: 'a', name: 'Harry', lastMessagedAt: '2016-03-14T15:00', isOnline: true }, 17 | { id: 'b', name: 'Bob', lastMessagedAt: '2017-01-20T12:00', isOnline: false }, 18 | { id: 'c', name: 'Molly', lastMessagedAt: '2013-01-02T03:04', isOnline: false }, 19 | { id: 'd', name: 'Karrin', lastMessagedAt: '2017-01-12T11:05', isOnline: true }, 20 | { id: 'e', name: 'Thomas', lastMessagedAt: '2014-03-04T13:37', isOnline: true }, 21 | ] 22 | 23 | const UserList = ({ users }) => ( 24 | 25 | 26 | 27 | {user =>
{user.name}
} 28 |
29 |
30 |
31 | ) 32 | 33 | ReactDOM.render( 34 | , 35 | document.querySelector('#root') 36 | ) 37 | 38 | /* 39 | Renders: 40 | 41 |
42 |
Karrin
43 |
Harry
44 |
Thomas
45 |
46 | */ 47 | ``` 48 | 49 | 50 | 51 | 52 | ## Live Demo 53 | 54 | **[Play with a live demo.](https://joshwcomeau.github.io/react-collection-helpers/demo/dist/)** 55 | 56 | 57 | 58 | ## Table of Contents 59 | 60 | - [Features](#features) 61 | - [Drawbacks](#drawbacks) 62 | - [Installation](#installation) 63 | - [Feedback Wanted](#feedback-wanted) 64 | - [Usage](#usage) 65 | - [Guides](#guides) 66 | - [Understanding and Customizing Markup](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/markup.md) 67 | - [Predicates in React Collection Helpers](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/predicates.md) 68 | - [Comparators in React Collection Helpers](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/comparators.md) 69 | - [API Reference](#api-reference) 70 | - [``](#every) 71 | - [``](#filter) 72 | - [``](#find) 73 | - [``](#first) 74 | - [``](#last) 75 | - [``](#map) 76 | - [``](#reject) 77 | - [``](#reverse) 78 | - [``](#some) 79 | - [``](#sort) 80 | 81 | 82 | 83 | 84 | ## Features 85 | 86 | - :sparkles: **Useful** - includes 10+ components to help you filter, sort, and slice collections. 87 | - :black_nib: **Designer-friendly** - make your designers' lives easier by writing components without complex inline logic. 88 | - :zap: **Tiny** - full build is only 2.5kb, and is modular so you can import only the components you need. 89 | - :muscle: **Performant** - [experiments show](https://github.com/joshwcomeau/react-collection-helpers/blob/master/tools/performance-checks.js) that these components are just as performant as native methods. 90 | - :wrench: **Customizable** - the wrapper element can be any element type you'd like (native _or_ composite), and all non-recognized props are passed through. Composing Collection Helpers does not create additional HTML markup! 91 | 92 | 93 | 94 | 95 | ## Drawbacks 96 | 97 | So, React Collection Helpers is an experiment, designed to test if React's component model makes sense when used for array manipulation. 98 | 99 | I believe that it's nicer, for the [reasons outlined above](#features), than doing vanilla JS manipulation within your component's render method. That said, *this is often the wrong place to be doing this kind of logic*. 100 | 101 | For very large collections, or for components that re-render frequently, it makes sense to move this kind of intense manipulation into memoized functions. If you use Redux, then [reselect](https://github.com/reactjs/reselect) is likely a better place to do this kind of work. 102 | 103 | It also means that your presentation layer never has to concern itself with this work, which is usually a good thing. 104 | 105 | For small apps (or simple sections within large apps), React Collection Helpers can be a nice touch. For redux apps, though, there is often a better way. 106 | 107 | Ultimately, React Collection Helpers is built as a starting point, not a final destination. I feel like with enough trial and error, we might stumble upon something genuinely innovative and useful. I'm going to continue experimenting, and I would encourage us all to keep an open mind, and an eye out for exciting new possibilities. 108 | 109 | 110 | 111 | 112 | ## Installation 113 | 114 | ``` 115 | npm i -S react-collection-helpers 116 | ``` 117 | 118 | UMD builds are also available via CDN: 119 | 120 | - [react-collection-helpers.js](https://unpkg.com/react-collection-helpers@1.1.0/umd/react-collection-helpers.js) 121 | - [react-collection-helpers.min.js](https://unpkg.com/react-collection-helpers@1.1.0/umd/react-collection-helpers.js) 122 | 123 | (If you use the UMD build, the global variable is `CollectionHelpers`) 124 | 125 | 126 | 127 | 128 | ## Feedback Wanted 129 | 130 | This project is an experiment to test the usefulness of collection manipulators in component form factor. 131 | 132 | When I say that it's an experiment, I don't necessarily mean that it's _experimental_. I'm pretty confident that it's stable and safe to use in production; the code is quite simple. 133 | 134 | Rather, I mean that I'm not convinced that it solves a real problem. I'd like to hear from users who implement them; does it improve the development experience of you or your team? Do you think the idea has potential if it went in a certain direction? I'm open to exploring tangential ideas. 135 | 136 | Let me know [on Twitter](https://twitter.com/joshwcomeau), or [via email](mailto:joshwcomeau+rch@gmail.com) 137 | 138 | 139 | 140 | 141 | ## Usage 142 | 143 | Import the component(s) you need: 144 | 145 | ```jsx 146 | // ES6 modules 147 | import { Find, Every, Map } from 'react-collection-helpers'; 148 | 149 | // CommonJS 150 | const { Find, Every, Map } = require('react-collection-helpers'); 151 | ``` 152 | 153 | Alternatively, you can import components individually, to avoid bundling the components you don't use: 154 | 155 | ```jsx 156 | // This method avoids bundling unused components, and reduces gzipped bundles 157 | // by about 1kb. 158 | import Find from 'react-collection-helpers/lib/components/Find'; 159 | import Every from 'react-collection-helpers/lib/components/Every'; 160 | import Map from 'react-collection-helpers/lib/components/Map'; 161 | ``` 162 | 163 | 164 | ## Guides 165 | 166 | Learn more about how best to use React Collection Helpers with these in-depth guides: 167 | 168 | * [Understanding and Customizing Markup](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/markup.md) 169 | * [Predicates in React Collection Helpers](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/predicates.md) 170 | * [Comparators in React Collection Helpers](https://github.com/joshwcomeau/react-collection-helpers/blob/master/documentation/comparators.md) 171 | 172 | 173 | 174 | ## API Reference 175 | 176 | ### `` 177 | 178 | Render the children if the predicate returns true for **every** child. A Fallback node can be provided, to be rendered if the predicate returns false for any child. Otherwise, nothing will be rendered. 179 | 180 | If no predicate is provided, the content will be rendered as long as the collection has 1 item or more. If an empty collection is supplied, the fallback content will be rendered. 181 | 182 | #### Props 183 | 184 | | Prop | Required | Types | Notes | 185 | |--------------|----------|---------|----------| 186 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 187 | | `predicate` | ✕ | `function`/`object`| See [predicates](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/predicates.md) for more information | 188 | | `fallback` | ✕ | `node` | Alternate content to be rendered if the predicate returns false on any items. 189 | 190 | #### Example 191 | 192 | ```jsx 193 | const collection = [ 194 | { id: 'a', src: '...', isLoaded: true }, 195 | { id: 'b', src: '...', isLoaded: true }, 196 | { id: 'c', src: '...', isLoaded: false }, 197 | ]; 198 | 199 | Loading...} 203 | > 204 | {item => } 205 | 206 | ``` 207 | 208 | 209 | ### `` 210 | 211 | Render only the children for which the predicate returns `true`. 212 | 213 | 214 | #### Props 215 | 216 | | Prop | Required | Types | Notes | 217 | |--------------|----------|---------|----------| 218 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 219 | | `predicate` | ✓ | `function`/`object`| See [predicates](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/predicates.md) for more information | 220 | 221 | #### Example 222 | 223 | ```jsx 224 | const collection = [ 225 | { id: 'a', name: 'apple', price: 1.00 }, 226 | { id: 'b', name: 'banana', price: 5.00 }, 227 | { id: 'c', name: 'carrot', price: 2.50 }, 228 | ]; 229 | 230 | (item.price < 3)}> 231 | {item =>
{item.name}
} 232 |
233 | ``` 234 | 235 | 236 | ### `` 237 | 238 | Render the first child for which the predicate returns `true`. 239 | 240 | 241 | #### Props 242 | 243 | | Prop | Required | Types | Notes | 244 | |--------------|----------|---------|----------| 245 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 246 | | `predicate` | ✓ | `function`/`object`| See [predicates](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/predicates.md) for more information | 247 | 248 | #### Example 249 | 250 | ```jsx 251 | const collection = [ 252 | { id: 'a', name: 'John', isAdmin: false }, 253 | { id: 'b', name: 'Jane', isAdmin: true }, 254 | { id: 'c', name: 'Jala', isAdmin: false }, 255 | ]; 256 | 257 | 258 | {user =>
Your group's admin is {user.name}
} 259 |
260 | ``` 261 | 262 | 263 | ### `` 264 | 265 | Returns the first 1 or more items of the collection. Generally only useful as a child to another Collection Helper. 266 | 267 | 268 | #### Props 269 | 270 | | Prop | Required | Types | Notes | 271 | |--------------|----------|---------|----------| 272 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 273 | | `num` | ✕ | `number`| Defaults to `1` | 274 | 275 | #### Example 276 | 277 | ```jsx 278 | const collection = [ 279 | { id: 'a', name: 'John', distance: 3.14 }, 280 | { id: 'b', name: 'Jane', distance: 0.45 }, 281 | { id: 'c', name: 'Jala', distance: 1.23 }, 282 | ]; 283 | 284 | 285 | 286 | {user =>
You are closest to {user.name}
} 287 |
288 |
289 | ``` 290 | 291 | 292 | ### `` 293 | 294 | Returns the last 1 or more items of the collection. The opposite of ``. Generally only useful as a child to another Collection Helper. 295 | 296 | 297 | #### Props 298 | 299 | | Prop | Required | Types | Notes | 300 | |--------------|----------|---------|----------| 301 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 302 | | `num` | ✕ | `number`| Defaults to `1` | 303 | 304 | #### Example 305 | 306 | ```jsx 307 | const collection = [ 308 | { id: 'a', name: 'John', distance: 3.14 }, 309 | { id: 'b', name: 'Jane', distance: 0.45 }, 310 | { id: 'c', name: 'Jala', distance: 1.23 }, 311 | ]; 312 | 313 | 314 | 315 | {user =>
You are furthest from {user.name}
} 316 |
317 |
318 | ``` 319 | 320 | 321 | ### `` 322 | 323 | The simplest Collection Helper, doesn't do very much. Can be useful to ensure consistency between your components. 324 | 325 | #### Props 326 | 327 | | Prop | Required | Types | Notes | 328 | |--------------|----------|---------|----------| 329 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 330 | 331 | #### Example 332 | 333 | ```jsx 334 | const collection = [ 335 | { id: 'a', name: 'John' }, 336 | { id: 'b', name: 'Jane' }, 337 | { id: 'c', name: 'Jala' }, 338 | ]; 339 | 340 | 341 | {user =>
{user.name}
} 342 |
343 | ``` 344 | 345 | 346 | ### `` 347 | 348 | Render only the children for which the predicate returns `false`. The opposite of ``. 349 | 350 | #### Props 351 | 352 | | Prop | Required | Types | Notes | 353 | |--------------|----------|---------|----------| 354 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 355 | | `predicate` | ✓ | `function`/`object`| See [predicates](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/predicates.md) for more information | 356 | 357 | #### Example 358 | 359 | ```jsx 360 | const collection = [ 361 | { id: 'a', name: 'apple', price: 1.00 }, 362 | { id: 'b', name: 'banana', price: 5.00 }, 363 | { id: 'c', name: 'carrot', price: 2.50 }, 364 | ]; 365 | 366 | (item.price > 3)}> 367 | {item =>
{item.name}
} 368 |
369 | ``` 370 | 371 | 372 | ### `` 373 | 374 | Render the children if the predicate returns true for **any** child. A Fallback node can be provided, to be rendered if the predicate returns false for all children. Otherwise, nothing will be rendered. 375 | 376 | If no predicate is provided, the content will be rendered as long as the collection has 1 item or more. If an empty collection is supplied, the fallback content will be rendered. 377 | 378 | #### Props 379 | 380 | | Prop | Required | Types | Notes | 381 | |--------------|----------|---------|----------| 382 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 383 | | `predicate` | ✕ | `function`/`object`| See [predicates](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/predicates.md) for more information | 384 | | `fallback` | ✕ | `node` | Alternate content to be rendered if the predicate returns false on all items. 385 | 386 | #### Example 387 | 388 | ```jsx 389 | const collection = [ 390 | { id: 'a', username: 'sickskillz', hasWon: false }, 391 | { id: 'b', username: 'dabomb12345', hasWon: false }, 392 | ]; 393 | 394 | 399 | {user => } 400 | 401 | ``` 402 | 403 | 404 | ### `` 405 | 406 | Sorts the children based on a comparator. 407 | 408 | #### Props 409 | 410 | | Prop | Required | Types | Notes | 411 | |--------------|----------|---------|----------| 412 | | `collection` | ✓ | [`any`] | Can be implicitly passed by parent Collection Helpers 413 | | `comparator` | ✓ | `function`/`object`| See [comparators](https://github.com/joshwcomeau/react-collection-helpers/tree/master/documentation/comparators.md) for more information | 414 | | `descending` | ✕ | `boolean` | Whether to sort in descending order, when providing a 'string' comparator. Defaults to `false` (string comparators sort in ascending). 415 | 416 | #### Example 417 | 418 | ```jsx 419 | const collection = [ 420 | { id: 'a', name: 'apple', price: 1.00 }, 421 | { id: 'b', name: 'banana', price: 5.00 }, 422 | { id: 'c', name: 'carrot', price: 2.50 }, 423 | ]; 424 | 425 | 426 | {item => } 427 | 428 | ``` 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | [build-badge]: https://img.shields.io/travis/joshwcomeau/react-collection-helpers/master.png?style=flat-square 437 | [build]: https://travis-ci.org/joshwcomeau/react-collection-helpers 438 | 439 | [npm-badge]: https://img.shields.io/npm/v/react-collection-helpers.png?style=flat-square 440 | [npm]: https://www.npmjs.org/package/react-collection-helpers 441 | 442 | [coveralls-badge]: https://img.shields.io/coveralls/joshwcomeau/react-collection-helpers/master.png?style=flat-square 443 | [coveralls]: https://coveralls.io/github/joshwcomeau/react-collection-helpers 444 | -------------------------------------------------------------------------------- /demo/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshwcomeau/react-collection-helpers/335b56aa9b569de013770ab32859da559efce36c/demo/favicon.png -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Collection Helpers 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/assets/DSC_8122_1024x1024.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshwcomeau/react-collection-helpers/335b56aa9b569de013770ab32859da559efce36c/demo/src/assets/DSC_8122_1024x1024.jpeg -------------------------------------------------------------------------------- /demo/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/assets/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/components/App/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | 4 | import Nav from '../Nav'; 5 | import Header from '../Header'; 6 | import MaxWidthWrapper from '../MaxWidthWrapper'; 7 | import ComponentName from '../ComponentName'; 8 | import FiddleEmbed from '../FiddleEmbed'; 9 | import Footer from '../Footer'; 10 | import './App.scss'; 11 | 12 | 13 | const App = () => ( 14 |
15 |
154 | ); 155 | 156 | export default App; 157 | -------------------------------------------------------------------------------- /demo/src/components/App/App.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | @import '../../style-variables'; 6 | 7 | 8 | html, body, div, span, applet, object, iframe, 9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 10 | a, abbr, acronym, address, big, cite, code, 11 | del, dfn, em, img, ins, kbd, q, s, samp, 12 | small, strike, strong, sub, sup, tt, var, 13 | b, u, i, center, 14 | dl, dt, dd, ol, ul, li, 15 | fieldset, form, label, legend, 16 | table, caption, tbody, tfoot, thead, tr, th, td, 17 | article, aside, canvas, details, embed, 18 | figure, figcaption, footer, header, hgroup, 19 | menu, nav, output, ruby, section, summary, 20 | time, mark, audio, video { 21 | margin: 0; 22 | padding: 0; 23 | border: 0; 24 | font-size: 16px; 25 | vertical-align: baseline; 26 | } 27 | /* HTML5 display-role reset for older browsers */ 28 | article, aside, details, figcaption, figure, 29 | footer, header, hgroup, menu, nav, section { 30 | display: block; 31 | } 32 | body { 33 | line-height: 1.5; 34 | } 35 | 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | blockquote:before, blockquote:after, 40 | q:before, q:after { 41 | content: ''; 42 | content: none; 43 | } 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } 48 | 49 | // Global styles 50 | *, *:before, *:after { 51 | box-sizing: border-box; 52 | font-family: -apple-system, BlinkMacSystemFont, 53 | "Segoe UI", "Roboto", "Oxygen", 54 | "Ubuntu", "Cantarell", "Fira Sans", 55 | "Droid Sans", "Helvetica Neue", sans-serif; 56 | } 57 | 58 | body { 59 | background: $offwhite; 60 | } 61 | 62 | h1, h2, h3, h4, h5, h6 { 63 | font-family: 'Lora', serif; 64 | font-weight: bold; 65 | margin-bottom: 0.4rem; 66 | margin-top: 3.5rem; 67 | } 68 | 69 | a { 70 | position: relative; 71 | display: inline-block; 72 | color: $black; 73 | transition: color 250ms; 74 | text-decoration: none; 75 | } 76 | 77 | a:after { 78 | content: ''; 79 | position: absolute; 80 | left: 0; 81 | right: 0; 82 | bottom: 0; 83 | height: 3px; 84 | border-radius: 2px; 85 | background: $red; 86 | opacity: 0.2; 87 | transition: opacity 350ms, transform 500ms ease-in; 88 | transform: scaleY(0.66); 89 | } 90 | 91 | a:hover { 92 | color: $red; 93 | } 94 | 95 | a:hover:after { 96 | opacity: 1; 97 | transform: scaleY(1); 98 | transition: opacity 350ms, transform 500ms ease-out; 99 | 100 | } 101 | 102 | ol, ul { 103 | color: $darkgray; 104 | padding: 1rem 2rem; 105 | } 106 | 107 | li { 108 | margin-bottom: 1rem; 109 | } 110 | 111 | h1 { 112 | font-size: 44px; 113 | } 114 | 115 | h2 { 116 | font-size: 38px; 117 | } 118 | 119 | h3 { 120 | font-size: 32px; 121 | } 122 | 123 | h4 { 124 | font-size: 26px; 125 | } 126 | 127 | h5 { 128 | font-size: 20px; 129 | } 130 | 131 | h6 { 132 | font-size: 16px; 133 | } 134 | 135 | p { 136 | color: $darkgray; 137 | margin-bottom: 1.25rem; 138 | } 139 | 140 | .mono { 141 | font-family: monospace; 142 | } 143 | 144 | .contact-info { 145 | font-size: 18px; 146 | } 147 | 148 | .centered { 149 | text-align: center; 150 | } 151 | 152 | @media (max-width: 600px) { 153 | h1 { 154 | font-size: 32px; 155 | } 156 | 157 | .contact-info br { 158 | display: none; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /demo/src/components/App/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './App'; 2 | -------------------------------------------------------------------------------- /demo/src/components/ComponentName/ComponentName.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import './ComponentName.scss'; 4 | 5 | 6 | const ComponentName = ({ children }) => ( 7 | 11 | {`<${children}>`} 12 | 13 | ); 14 | 15 | ComponentName.propTypes = { 16 | children: PropTypes.string.isRequired, 17 | }; 18 | 19 | export default ComponentName; 20 | -------------------------------------------------------------------------------- /demo/src/components/ComponentName/ComponentName.scss: -------------------------------------------------------------------------------- 1 | .ComponentName { 2 | background: rgba(0,0,0,0.055); 3 | font-family: monospace; 4 | padding: 3px 7px; 5 | border-radius: 3px; 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/components/ComponentName/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './ComponentName'; 2 | -------------------------------------------------------------------------------- /demo/src/components/FiddleEmbed/FiddleEmbed.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import './FiddleEmbed.scss'; 4 | 5 | 6 | const FiddleEmbed = ({ 7 | fiddleId, 8 | height, 9 | revision, 10 | panes, 11 | bodyColor, 12 | accentColor, 13 | menuColor, 14 | children, 15 | }) => { 16 | let url = `//jsfiddle.net/joshwcomeau/${fiddleId}/`; 17 | 18 | if (revision) { 19 | url += `${revision}/`; 20 | } 21 | 22 | url += `embedded/${panes.join(',')}/? 23 | bodyColor=${bodyColor}& 24 | accentColor=${accentColor}& 25 | menuColor=${menuColor}`.replace(/\s/g, ''); 26 | 27 | return ( 28 |
29 |