├── .gitignore ├── README.md ├── examples ├── collection.js ├── drag-and-drop-key-points.js ├── dynamic-cell-measurer.js ├── dynamic-row-height-getter.js ├── element-child.js ├── force-update.js ├── forgotten-styles.js ├── function-child.js ├── function-children-example-1.js ├── function-children-example-2.js ├── function-children-example-3.js ├── is-scrolling-cell-renderer.js ├── multi-grid-renderers.js ├── multi-grid.js ├── pass-through-props.js ├── perf-comparison-non-virtualized.js ├── perf-comparison-plain.js ├── perf-comparison-virtualized.js ├── pure-component.js ├── resizable-cells-key-points.js ├── row-renderer.js └── shallow-compare.js ├── lib └── font-awesome │ ├── css │ ├── font-awesome.css │ └── font-awesome.min.css │ └── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── package.json ├── public ├── books.jpg ├── browser-limits-cutoff.png ├── cache-all-the-things.png ├── computer-guy.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── how-is-it-used-calendar.gif ├── how-is-it-used-dropdown.gif ├── how-is-it-used-table.gif ├── how-is-it-used-tree.gif ├── index.html ├── kindle.jpg ├── non-uniform-heights-chat.png ├── non-uniform-heights-drop-down.png ├── occlusion-culling.jpg ├── overscan-example.mp4 ├── profile-picture.jpg └── v9-dev-mode-style-warning.png ├── src ├── App.js ├── Components │ ├── AnimatedList.css │ ├── AnimatedList.js │ ├── AnimatedListRow.css │ ├── AnimatedListRow.js │ ├── BuildingBlocks.css │ ├── BuildingBlocks.js │ ├── BuildingBlocksSvgWrapper.css │ ├── BuildingBlocksSvgWrapper.js │ ├── CrossHatchRect.css │ ├── CrossHatchRect.js │ ├── DeferMeasurements.js │ ├── DemoCollection.css │ ├── DemoCollection.js │ ├── DragAndDropList.css │ ├── DragAndDropList.js │ ├── ExampleList.js │ ├── HowDoesWindowingWork.css │ ├── HowDoesWindowingWork.js │ ├── LabeledCircle.js │ ├── LabeledRect.js │ ├── LabeledSvg.css │ ├── MarioSvg.js │ ├── Note.js │ ├── ResizableRowsList.css │ ├── ResizableRowsList.js │ ├── ScaledList.css │ ├── ScaledList.js │ ├── Scaler.js │ ├── ScuChart.css │ ├── ScuChart.js │ ├── Spreadsheet.css │ ├── Spreadsheet.js │ ├── StyledListElements.js │ ├── SvgWrapper.js │ └── TreasureDataIcon.js ├── ForwardTheme.js ├── Presentation │ ├── ContentSlide.js │ ├── TitleSlide.js │ └── presenterSlideStyle.js ├── Slides │ ├── 000.js │ ├── 001.js │ ├── 002.js │ ├── 003.js │ ├── 004.js │ ├── 100.js │ ├── 101.js │ ├── 102.js │ ├── 103.js │ ├── 104.js │ ├── 200.js │ ├── 201.js │ ├── 202.js │ ├── 203.js │ ├── 204.js │ ├── 205.js │ ├── 206.js │ ├── 300.js │ ├── 301.js │ ├── 400.js │ ├── 401.js │ ├── 402.js │ ├── 403.js │ ├── 404.js │ ├── 405.js │ ├── 406.js │ ├── 500.js │ ├── 501.js │ ├── 502.js │ ├── 503.js │ ├── 504.js │ ├── 600.js │ ├── 601.js │ ├── 602.js │ └── 700.js ├── Utils │ └── generateRandomList.js ├── index.js └── shared-list-styles.css └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/collection.js: -------------------------------------------------------------------------------- 1 | function cellSizeAndPositionGetter ({ index }) { 2 | const item = itemsArray[index] 3 | 4 | return { 5 | height: item.size, 6 | width: item.size, 7 | x: item.left, 8 | y: item.top 9 | } 10 | } 11 | 12 | function cellRenderer ({ index, key, style }) { 13 | return ( 14 |
18 | {itemsArray[index].name} 19 |
20 | ) 21 | } 22 | 23 | function renderCollection (props) { 24 | return ( 25 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /examples/drag-and-drop-key-points.js: -------------------------------------------------------------------------------- 1 | import { arrayMove, SortableContainer, SortableElement } from 'react-sortable-hoc' 2 | import { List } from 'react-virtualized' 3 | 4 | // Connect react-virtualized and react-sortable-hoc 5 | const SortableList = SortableContainer(List, { withRef: true }) 6 | const SortableRow = SortableElement(({ children }) => children) 7 | 8 | function rowRenderer ({ index, key, style }) { 9 | return ( 10 |
11 | 12 | {itemsArray[index].name} 13 | 14 |
15 | ) 16 | } 17 | 18 | function renderList (props) { 19 | return ( 20 | arrayMove(list, oldIndex, newIndex) 24 | } 25 | rowRenderer={rowRenderer} 26 | /> 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /examples/dynamic-cell-measurer.js: -------------------------------------------------------------------------------- 1 | const cache = new CellMeasurerCache({ fixedWidth: true }); 2 | 3 | function rowRenderer ({ index, key, parent, style }) { 4 | return ( 5 | 12 |
{/* Your content here */}
13 |
14 | ); 15 | } 16 | 17 | function renderList (props) { 18 | return ( 19 | 25 | ); 26 | } -------------------------------------------------------------------------------- /examples/dynamic-row-height-getter.js: -------------------------------------------------------------------------------- 1 | function getRowHeight ({ index }) { 2 | return itemArray[index].type === 'header' ? 20 : 30 3 | } 4 | 5 | function renderList (listProps) { 6 | return ( 7 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/element-child.js: -------------------------------------------------------------------------------- 1 |
2 | Brian 3 |
-------------------------------------------------------------------------------- /examples/force-update.js: -------------------------------------------------------------------------------- 1 | class Example extends Component { 2 | componentDidUpdate (prevProps, prevState) { 3 | if (this.props.sortOrder !== prevProps.sortOrder) { 4 | this._grid.forceUpdate() 5 | } 6 | } 7 | 8 | render () { 9 | return ( 10 | this._grid = ref} 13 | /> 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/forgotten-styles.js: -------------------------------------------------------------------------------- 1 | // Wrong: 2 | function rowRenderer ({ key, index, style }) { 3 | return ( 4 |
5 | {items[index]} 6 |
7 | ) 8 | } 9 | 10 | // Right: 11 | function rowRenderer ({ key, index, style }) { 12 | return ( 13 |
17 | {items[index]} 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /examples/function-child.js: -------------------------------------------------------------------------------- 1 |
2 | {(name) => {name}} 3 |
-------------------------------------------------------------------------------- /examples/function-children-example-1.js: -------------------------------------------------------------------------------- 1 | // Pseudo-code 2 | class WithUser extends Component { 3 | componentDidMount () { 4 | // Load data... 5 | } 6 | 7 | render () { 8 | const { user } = this.state 9 | const { children } = this.props 10 | 11 | return user 12 | ? children(user) 13 | : null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/function-children-example-2.js: -------------------------------------------------------------------------------- 1 | // Pseudo-code 2 | class WithLocalization extends Component { 3 | componentDidMount () { 4 | // Load data... 5 | } 6 | 7 | render () { 8 | const { localization } = this.state 9 | const { children } = this.props 10 | 11 | return localization 12 | ? children(localization) 13 | : null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/function-children-example-3.js: -------------------------------------------------------------------------------- 1 | function LocalizedUserBadge () { 2 | return ( 3 | 4 | {(localization) => ( 5 | 6 | {(user) => ( 7 |

8 | {localization.get('user_greeting', { name: user.name })} 9 |

10 | )} 11 |
12 | )} 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/is-scrolling-cell-renderer.js: -------------------------------------------------------------------------------- 1 | function rowRenderer ({ isScrolling, ...rest }) { 2 | return isScrolling 3 | ? 4 | : 5 | } 6 | -------------------------------------------------------------------------------- /examples/multi-grid-renderers.js: -------------------------------------------------------------------------------- 1 | function cellRendererBody ({ columnIndex, key, rowIndex, style }) { 2 | return ( 3 | updateCellValue(key, event.target.value) 7 | } 8 | style={style} 9 | value={getCellValue(key)} 10 | /> 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/multi-grid.js: -------------------------------------------------------------------------------- 1 | function renderSpreadsheet () { 2 | return ( 3 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /examples/pass-through-props.js: -------------------------------------------------------------------------------- 1 | function renderList (props) { 2 | const { sortOrder, ...rest } = props 3 | 4 | return ( 5 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/perf-comparison-non-virtualized.js: -------------------------------------------------------------------------------- 1 | const itemsArray = [...] // This is our data 2 | 3 | function rowRenderer ({ index }) { 4 | return ( 5 |
6 | {itemsArray[index].name} 7 |
8 | ) 9 | } 10 | 11 | function renderList () { 12 | return ( 13 |
19 | {itemsArray.map((item, index) => rowRenderer({ index }))} 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /examples/perf-comparison-plain.js: -------------------------------------------------------------------------------- 1 | const itemsArray = [...] // This is our data 2 | 3 | function renderList () { 4 | return ( 5 |
11 | {itemsArray.map((item, index) => { 12 |
13 | {item.name} 14 |
15 | })} 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /examples/perf-comparison-virtualized.js: -------------------------------------------------------------------------------- 1 | const itemsArray = [...] // This is our data 2 | 3 | function rowRenderer ({ index, key, style }) { 4 | return ( 5 |
9 | {itemsArray[index].name} 10 |
11 | ) 12 | } 13 | 14 | function renderList () { 15 | return ( 16 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /examples/pure-component.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | class MyComponent extends PureComponent { 4 | render () { 5 | // Your render logic here 6 | } 7 | } -------------------------------------------------------------------------------- /examples/resizable-cells-key-points.js: -------------------------------------------------------------------------------- 1 | import Draggable from 'react-draggable'; 2 | import { List } from 'react-virtualized'; 3 | 4 | function rowRenderer ({ index, isScrolling, key, style }) { 5 | return ( 6 |
10 | {...} 11 | 12 | updateRowSize({ delta: data.y, index }) 16 | } 17 | /> 18 |
19 | ); 20 | } 21 | 22 | function renderList (listProps) { 23 | return ( 24 | this._list = ref} 27 | rowRenderer={rowRenderer} 28 | /> 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /examples/row-renderer.js: -------------------------------------------------------------------------------- 1 | function rowRenderer ({ key, index, style }) { 2 | return ( 3 |
7 | {itemsArray[index].name} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/shallow-compare.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import shallowCompare from 'react-addons-shallow-compare' 3 | 4 | class MyComponent extends Component { 5 | shouldComponentUpdate(nextProps, nextState) { 6 | return shallowCompare(this, nextProps, nextState) 7 | } 8 | 9 | render () { 10 | // Your render logic here 11 | } 12 | } -------------------------------------------------------------------------------- /lib/font-awesome/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /lib/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/lib/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /lib/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/lib/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /lib/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/lib/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /lib/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/lib/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /lib/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/lib/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forward-js-2017", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "react-addons-shallow-compare": "^15.3.2", 7 | "react-scripts": "0.6.1" 8 | }, 9 | "homepage": "https://bvaughn.github.io/forward-js-2017/", 10 | "dependencies": { 11 | "classnames": "^2.2.5", 12 | "codemirror": "^5.19.0", 13 | "immutable": "^3.8.1", 14 | "lodash.times": "^4.3.2", 15 | "numeral": "^1.5.3", 16 | "performance-now": "^0.2.0", 17 | "raw-loader": "^0.5.1", 18 | "react": "^15.3.2", 19 | "react-addons-shallow-compare": "^15.3.2", 20 | "react-codemirror": "^0.2.6", 21 | "react-dom": "^15.3.2", 22 | "react-draggable": "^2.2.2", 23 | "react-presents": "0.7.6", 24 | "react-radio-group": "^3.0.1", 25 | "react-router-dom": "4.0.0-beta.6", 26 | "react-sortable-hoc": "0.0.9", 27 | "react-virtualized": "^9.0.0", 28 | "react-virtualized-select": "^2.1.0", 29 | "spectacle": "^1.2.3" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build", 34 | "postbuild": "git checkout -B gh-pages && git add -f build && git commit -am 'Rebuild website' && git filter-branch -f --prune-empty --subdirectory-filter build && git push -f origin gh-pages && git checkout -", 35 | "test": "react-scripts test --env=jsdom", 36 | "eject": "react-scripts eject" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/books.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/books.jpg -------------------------------------------------------------------------------- /public/browser-limits-cutoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/browser-limits-cutoff.png -------------------------------------------------------------------------------- /public/cache-all-the-things.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/cache-all-the-things.png -------------------------------------------------------------------------------- /public/computer-guy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/computer-guy.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/favicon.ico -------------------------------------------------------------------------------- /public/how-is-it-used-calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/how-is-it-used-calendar.gif -------------------------------------------------------------------------------- /public/how-is-it-used-dropdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/how-is-it-used-dropdown.gif -------------------------------------------------------------------------------- /public/how-is-it-used-table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/how-is-it-used-table.gif -------------------------------------------------------------------------------- /public/how-is-it-used-tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/how-is-it-used-tree.gif -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | Forward JS 21 | 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/kindle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/kindle.jpg -------------------------------------------------------------------------------- /public/non-uniform-heights-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/non-uniform-heights-chat.png -------------------------------------------------------------------------------- /public/non-uniform-heights-drop-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/non-uniform-heights-drop-down.png -------------------------------------------------------------------------------- /public/occlusion-culling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/occlusion-culling.jpg -------------------------------------------------------------------------------- /public/overscan-example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/overscan-example.mp4 -------------------------------------------------------------------------------- /public/profile-picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/profile-picture.jpg -------------------------------------------------------------------------------- /public/v9-dev-mode-style-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/public/v9-dev-mode-style-warning.png -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { DropDownNav, Presentation, Slide } from 'react-presents'; 3 | import generateRandomList from './Utils/generateRandomList'; 4 | import ForwardTheme from './ForwardTheme'; 5 | 6 | // Load all slides in the Slides folder 7 | const slides = require.context('./Slides/', false, /\.js$/) 8 | .keys() 9 | .map((filename) => filename.replace('./', '')) 10 | .map((filename) => require(`./Slides/${filename}`).default); 11 | 12 | // Support navigating to any slides also tagged with a :title 13 | const options = slides 14 | .map((slide, index) => ({ 15 | label: slide.title, 16 | value: index 17 | })) 18 | .filter((option) => option.label); 19 | 20 | // Test data for use in performance examples 21 | const list = generateRandomList(); 22 | 23 | export default class App extends Component { 24 | static childContextTypes = { 25 | list: PropTypes.array.isRequired 26 | }; 27 | 28 | getChildContext () { 29 | return { 30 | list 31 | }; 32 | } 33 | 34 | render() { 35 | return ( 36 | 37 | {slides.map((Component, index) => ( 38 | 42 | )).concat( 43 | 47 | )} 48 | 49 | 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Components/AnimatedList.css: -------------------------------------------------------------------------------- 1 | .AnimatedList { 2 | position: relative; 3 | width: 150px; 4 | height: 300px; 5 | overflow: visible; 6 | margin: 8px; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 8 | font-size: 12px; 9 | } 10 | 11 | .Positioned { 12 | overflow: visible; 13 | } 14 | 15 | .InnerFrame { 16 | position: absolute; 17 | top: 100px; 18 | height: 100px; 19 | left: -8px; 20 | right: -8px; 21 | border: 4px solid #222; 22 | border-radius: 8px; 23 | } 24 | 25 | .AnimatedListDimmerBottom, 26 | .AnimatedListDimmerTop { 27 | position: absolute; 28 | left: 0; 29 | right: 0; 30 | background-color: #fff; 31 | opacity: 0.35; 32 | } 33 | 34 | .AnimatedListDimmerBottom { 35 | top: 200px; 36 | height: 130px; 37 | } 38 | 39 | 40 | .AnimatedListDimmerTop { 41 | top: 0; 42 | height: 100px; 43 | } 44 | -------------------------------------------------------------------------------- /src/Components/AnimatedList.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import times from 'lodash.times'; 3 | import React from 'react'; 4 | import Row from './AnimatedListRow'; 5 | import './AnimatedList.css'; 6 | 7 | // Assumes 10 total rows; renders 11 to allow for partial visibility of first and last row 8 | // Assumes 30px row height, 1 overscan, 2 hidden rows, 4 visible rows (10 total rows) 9 | export default function AnimatedListList ({ 10 | className = '', 11 | direction = 0, 12 | iteration = 0, 13 | offset = 0, 14 | overscanCount = 2 15 | }) { 16 | const visibleRows = []; 17 | for (let index = 0; index < 11; index++) { 18 | const rowTop = index * 30 + offset; 19 | const rowBottom = rowTop + 30; 20 | 21 | // 90..210 allows enough room for 4 fully visible rows (5 with partials) 22 | if (!(rowBottom <= 100 || rowTop >= 200)) { 23 | visibleRows.push(index); 24 | } 25 | } 26 | 27 | const overscanRows = []; 28 | const firstVisibleRow = visibleRows[0]; 29 | const lastVisibleRow = visibleRows[visibleRows.length - 1]; 30 | switch (direction) { 31 | case -1: // Scrolling up 32 | for (let i = 1; i <= overscanCount; i++) { 33 | overscanRows.push(firstVisibleRow - i); 34 | } 35 | break; 36 | case 1: // Scrolling down 37 | for (let i = 1; i <= overscanCount; i++) { 38 | overscanRows.push(lastVisibleRow + i); 39 | } 40 | break; 41 | default: // Resting 42 | for (let i = 1; i <= overscanCount / 2; i++) { 43 | overscanRows.push(firstVisibleRow - i); 44 | overscanRows.push(lastVisibleRow + i); 45 | } 46 | break; 47 | } 48 | 49 | return ( 50 |
51 |
57 | {times(11).map((index) => { 58 | const isVisibleRow = visibleRows.includes(index); 59 | const isOverscanRow = overscanRows.includes(index); 60 | const isInvisibleRow = !isVisibleRow && !isOverscanRow; 61 | 62 | let label; 63 | if (isOverscanRow) { 64 | label = 'rendered (overscan)'; 65 | } else if (isInvisibleRow) { 66 | label = 'not rendered'; 67 | } else { 68 | label = 'rendered'; 69 | } 70 | 71 | return ( 72 | 80 | ) 81 | })} 82 |
83 | 84 |
85 |
86 | 87 |
88 |
89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/Components/AnimatedListRow.css: -------------------------------------------------------------------------------- 1 | .AnimatedListRow { 2 | display: inline-flex; 3 | align-items: center; 4 | padding: 0 0.5rem; 5 | background-color: #a2d4da; 6 | width: 100%; 7 | height: 26px; 8 | margin: 2px 0; 9 | transition: 0 all; 10 | } 11 | 12 | .AnimatedList--Overscan { 13 | background-color: #C0E0D0; 14 | } 15 | 16 | .AnimatedList--Invisible { 17 | background-color: #aaa; 18 | } 19 | 20 | .AnimatedList--Overscan, 21 | .AnimatedList--Invisible { 22 | transition: 500ms all; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/Components/AnimatedListRow.js: -------------------------------------------------------------------------------- 1 | import cn from 'classnames'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import './AnimatedListRow.css'; 4 | 5 | export default class AnimatedListRow extends Component { 6 | static propTypes = { 7 | direction: PropTypes.number.isRequired, 8 | invisible: PropTypes.bool.isRequired, 9 | iteration: PropTypes.number.isRequired, 10 | label: PropTypes.string.isRequired, 11 | overscan: PropTypes.bool.isRequired 12 | }; 13 | 14 | componentWillUpdate (nextProps, nextState) { 15 | const { direction, invisible, iteration, overscan } = this.props; 16 | 17 | if (iteration !== nextProps.iteration) { 18 | this._transitionClass = null 19 | } else if (direction !== 0) { 20 | if (invisible && !nextProps.invisible) { 21 | this._transitionClass = 'TransitionToVisible' 22 | } else if (!overscan && nextProps.overscan) { 23 | this._transitionClass = 'TransitionToOverscan' 24 | } 25 | } 26 | } 27 | 28 | render () { 29 | const { invisible, label, overscan } = this.props; 30 | 31 | const className = cn('AnimatedListRow', this._transitionClass, { 32 | 'AnimatedList--Invisible': invisible, 33 | 'AnimatedList--Overscan': overscan 34 | }); 35 | 36 | return ( 37 |
38 | {label} 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Components/BuildingBlocks.css: -------------------------------------------------------------------------------- 1 | .svgOuterBox { 2 | fill: transparent; 3 | stroke: #222; 4 | stroke-width: 4px; 5 | rx: 8px; 6 | ry: 8px; 7 | } 8 | 9 | .svgCollectionBoxNotRendered, 10 | .svgGridBoxNotRendered, 11 | .svgTableColumnNotRendered, 12 | .svgListRowNotRendered { 13 | fill: #aaa; 14 | stroke: transparent; 15 | } 16 | 17 | .svgCollectionBox, 18 | .svgGridBox, 19 | .svgListRow, 20 | .svgTableColumn { 21 | fill: #C0E0D0; 22 | stroke: transparent; 23 | } 24 | 25 | .svgTableHeader { 26 | fill: transparent; 27 | stroke: #222; 28 | } 29 | 30 | .svgNotRenderedDimmer { 31 | fill: #fff; 32 | opacity: 0.65; 33 | } 34 | -------------------------------------------------------------------------------- /src/Components/BuildingBlocks.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React from 'react'; 3 | import BuildingBlocksSvgWrapper from './BuildingBlocksSvgWrapper'; 4 | import LabeledCircle from './LabeledCircle'; 5 | import LabeledRect from './LabeledRect'; 6 | import './BuildingBlocks.css'; 7 | 8 | const LIST_ROW_OFFSETS = [10, 60, 110, 160, 210, 260, 310, 360] 9 | 10 | export function ListSvg (props) { 11 | return ( 12 | 19 | {LIST_ROW_OFFSETS.map((yOffset, index) => ( 20 | = 6 29 | })} 30 | hidden={index >= 6} 31 | > 32 | {index < 6 33 | ? 'Row' 34 | : 'Not Rendered' 35 | } 36 | 37 | ))} 38 | 39 | 40 | ); 41 | } 42 | 43 | const TABLE_COLUMN_TUPLES = [[10, 128], [143, 127]] 44 | 45 | export function TableSvg (props) { 46 | return ( 47 | 54 | {LIST_ROW_OFFSETS.map((yOffset, index) => ( 55 | TABLE_COLUMN_TUPLES.map(([xOffset, width]) => ( 56 | = 6 66 | })} 67 | hidden={index >= 6} 68 | > 69 | {index === 0 && 'Header'} 70 | {index > 0 && index < 6 && 'Column'} 71 | {index >= 6 && 'Not Rendered'} 72 | 73 | )) 74 | ))} 75 | 76 | 77 | ); 78 | } 79 | 80 | 81 | const GRID_BOX_OFFSETS = [10, 105, 200] 82 | const HIDDEN_GRID_BOX_OFFSETS = [[295, 10], [295, 105], [295, 200], [10, 295], [105, 295], [200, 295], [295, 295]] 83 | 84 | export function GridSvg (props) { 85 | return ( 86 | 93 | {GRID_BOX_OFFSETS.map((xOffset, xIndex) => ( 94 | GRID_BOX_OFFSETS.map((yOffset, yIndex) => ( 95 | 103 | Cell 104 | 105 | )) 106 | ))} 107 | {HIDDEN_GRID_BOX_OFFSETS.map(([xOffset, yOffset], index) => ( 108 | 119 | ))} 120 | 121 | 122 | 123 | ); 124 | } 125 | 126 | export function CollectionSvg (props) { 127 | return ( 128 | 135 | 141 | Rendered 142 | 143 | 150 | Rendered 151 | 152 | 159 | Rendered 160 | 161 | 168 | Rendered 169 | 170 | 179 | 189 | 190 | 191 | 192 | ); 193 | } 194 | -------------------------------------------------------------------------------- /src/Components/BuildingBlocksSvgWrapper.css: -------------------------------------------------------------------------------- 1 | .BuildingBlocksSvgWrapper { 2 | max-width: 100%; 3 | } -------------------------------------------------------------------------------- /src/Components/BuildingBlocksSvgWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SvgWrapper from './SvgWrapper'; 3 | import './BuildingBlocksSvgWrapper.css'; 4 | 5 | export default function BuildingBlocksSvgWrapper ({ children, ...rest }) { 6 | return ( 7 | 15 | 16 | {children} 17 | 18 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/Components/CrossHatchRect.css: -------------------------------------------------------------------------------- 1 | .CrossHatchRectPattern { 2 | stroke: #fff; 3 | stroke-width:10; 4 | } 5 | 6 | .CrossHatchRectHidden, 7 | .CrossHatchRectVisible { 8 | transition: all 2.5s ease; 9 | } 10 | .CrossHatchRectHidden { 11 | opacity: 0; 12 | } 13 | .CrossHatchRectVisible { 14 | opacity: 0.25; 15 | } -------------------------------------------------------------------------------- /src/Components/CrossHatchRect.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React from 'react'; 3 | import './CrossHatchRect.css'; 4 | 5 | export default function CrossHatchRect ({ className, x, y, width, height, visible }) { 6 | className = classnames('CrossHatchRectHidden', className, { 7 | CrossHatchRectVisible: visible 8 | }) 9 | 10 | return ( 11 | 12 | 13 | 20 | 27 | 28 | 29 | 30 | 38 | 39 | ); 40 | } -------------------------------------------------------------------------------- /src/Components/DeferMeasurements.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React from 'react'; 3 | import CrossHatchRect from '../Components/CrossHatchRect'; 4 | import LabeledRect from '../Components/LabeledRect'; 5 | import SvgWrapper from '../Components/SvgWrapper'; 6 | 7 | const BOXES = [ 8 | [2, 40], 9 | [42, 20], 10 | [62, 35], 11 | [97, 30], 12 | [127, 20], 13 | [147, 40], 14 | [187, 40], 15 | [227, 40] 16 | ] 17 | 18 | export default function DeferMeasurements () { 19 | return ( 20 | 26 | 27 | 28 | {BOXES.map(([y, height], index) => ( 29 | 5, 38 | HowWorksRowRendered: index >= 2 && index <= 5 39 | })} 40 | > 41 | {index < 2 && 'Not rendered'} 42 | {index >= 2 && index <= 5 && 'Rendered'} 43 | {index > 5 && 'Estimated'} 44 | 45 | ))} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | <ul> 66 | 67 | 68 | 69 | 70 | ); 71 | } -------------------------------------------------------------------------------- /src/Components/DemoCollection.css: -------------------------------------------------------------------------------- 1 | .Collection { 2 | border: 1px solid #ddd; 3 | } 4 | 5 | .CollectionCell { 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: center; 10 | color: #fff; 11 | } 12 | 13 | .CollectionCellCircle { 14 | border-radius: 100%; 15 | } 16 | -------------------------------------------------------------------------------- /src/Components/DemoCollection.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import { AutoSizer, Collection } from 'react-virtualized'; 4 | import './DemoCollection.css'; 5 | 6 | export default class MySlide extends Component { 7 | static propTypes = { 8 | list: PropTypes.array 9 | }; 10 | 11 | constructor (props, context) { 12 | super(props, context); 13 | 14 | this._cellRenderer = this._cellRenderer.bind(this); 15 | this._cellSizeAndPositionGetter = this._cellSizeAndPositionGetter.bind(this); 16 | } 17 | 18 | componentWillUpdate (nextProps, nextState) { 19 | // HACK Weird React bug where this.state === null 20 | // Seems to happen when component is about to unmount. 21 | // Going to just hack around it for now... 22 | if (!this.state) { 23 | return; 24 | } 25 | 26 | const { cellValues, focusedColumnIndex, focusedRowIndex } = this.state; 27 | 28 | if ( 29 | focusedColumnIndex !== nextState.focusedColumnIndex || 30 | focusedRowIndex !== nextState.focusedRowIndex 31 | ) { 32 | this._leftGrid.forceUpdate(); 33 | this._mainGrid.forceUpdate(); 34 | this._topGrid.forceUpdate(); 35 | } else if (cellValues !== nextState.cellValues) { 36 | this._mainGrid.forceUpdate(); 37 | } 38 | } 39 | 40 | render () { 41 | const { list } = this.props; 42 | 43 | return ( 44 | 45 | {({ width }) => ( 46 | 54 | )} 55 | 56 | ) 57 | } 58 | 59 | _cellRenderer ({ index, key, style }) { 60 | const { list } = this.props; 61 | 62 | const datum = list[index]; 63 | 64 | // Customize style 65 | style.backgroundColor = datum.color; 66 | 67 | return ( 68 |
75 | {index} 76 |
77 | ) 78 | } 79 | 80 | _cellSizeAndPositionGetter ({ index }) { 81 | const { list } = this.props; 82 | 83 | const datum = list[index]; 84 | 85 | return { 86 | height: datum.size, 87 | width: datum.size, 88 | x: datum.left, 89 | y: datum.top 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Components/DragAndDropList.css: -------------------------------------------------------------------------------- 1 | .SortableListRow { 2 | cursor: pointer; 3 | font-size: 12px; 4 | user-select: none; 5 | border-bottom: 1px solid #e9e9e9; 6 | } 7 | 8 | .SortableListRow:hover { 9 | background-color: #e9e9e9; 10 | } 11 | 12 | .SortableListRowActive { 13 | font-size: 13px; 14 | background-color: #C0E0D0; 15 | overflow: hidden; 16 | } 17 | -------------------------------------------------------------------------------- /src/Components/DragAndDropList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { arrayMove, SortableContainer, SortableElement } from 'react-sortable-hoc'; 3 | import { List } from 'react-virtualized'; 4 | import './DragAndDropList.css'; 5 | 6 | // Connect react-virtualized and react-sortable-hoc 7 | const SortableList = SortableContainer(List, { 8 | withRef: true 9 | }); 10 | const SortableListRow = SortableElement(({ children }) => children); 11 | 12 | export default class DragAndDropList extends Component { 13 | static propTypes = { 14 | list: PropTypes.array 15 | }; 16 | 17 | constructor (props, context) { 18 | super(props, context); 19 | 20 | this.state = { 21 | listIndices: props.list.map((item, index) => index) 22 | }; 23 | 24 | this._onSortEnd = this._onSortEnd.bind(this) 25 | this._rowRenderer = this._rowRenderer.bind(this) 26 | } 27 | 28 | render () { 29 | const { list } = this.props; 30 | 31 | return ( 32 | 43 | ); 44 | } 45 | 46 | _onSortEnd ({ newIndex, oldIndex }) { 47 | const { list } = this.props; 48 | 49 | if (newIndex === oldIndex) { 50 | return; 51 | } 52 | 53 | arrayMove(list, oldIndex, newIndex); 54 | 55 | this.forceUpdate(); // Re-render 56 | } 57 | 58 | _rowRenderer ({ index, key, style }) { 59 | const { list } = this.props; 60 | 61 | return ( 62 |
66 | 67 |
68 | {list[index].name} 69 |
70 |
71 |
72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Components/ExampleList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { ListRow, ListRowActive, RowName, RowNumber, RowStack, RowSummary, VirtualList } from './StyledListElements'; 3 | import '../shared-list-styles.css'; 4 | 5 | export default class ExampleList extends Component { 6 | static contextTypes = { 7 | list: PropTypes.array 8 | }; 9 | 10 | static propTypes = { 11 | scrollToAlignment: PropTypes.string, 12 | scrollToIndex: PropTypes.number 13 | }; 14 | 15 | render () { 16 | const { list } = this.context; 17 | const { scrollToAlignment, scrollToIndex } = this.props; 18 | 19 | return ( 20 | { 26 | const RowComponent = index === scrollToIndex 27 | ? ListRowActive 28 | : ListRow 29 | 30 | return ( 31 | 35 | 40 | {list[index].name.substr(0, 1)} 41 | 42 | 43 | {list[index].name} 44 | This is row {index} 45 | 46 | 47 | ) 48 | }} 49 | scrollToAlignment={scrollToAlignment} 50 | scrollToIndex={scrollToIndex} 51 | width={240} 52 | /> 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Components/HowDoesWindowingWork.css: -------------------------------------------------------------------------------- 1 | .HowWorksGroup { 2 | transform: skewX(0deg); 3 | } 4 | 5 | .HowWorksGroupSkewed { 6 | transition: all 1s ease; 7 | transform: skewX(15deg); 8 | } 9 | 10 | .HowWorksOuterGroup { 11 | transition: transform 1s ease; 12 | transform: translate(-75px, -68px); 13 | } 14 | 15 | .HowWorksOuterRect { 16 | rx: 8px; 17 | ry: 8px; 18 | stroke-width: 4px; 19 | fill: transparent; 20 | stroke: #222; 21 | transition: transform 1s ease; 22 | } 23 | 24 | .HowWorksOuterRectShifted { 25 | transform: translate(0, -30px); 26 | } 27 | 28 | .HowWorksHidden { 29 | opacity: 0; 30 | transition: all 1s ease; 31 | } 32 | 33 | .HowWorksVisible { 34 | opacity: 1; 35 | transition: all 1s ease; 36 | } 37 | 38 | .HowWorksInnerLineAnimated { 39 | animation: scrolling 1s linear; 40 | animation-iteration-count: infinite; 41 | } 42 | 43 | @keyframes scrolling { 44 | from { 45 | transform: translateY(0); 46 | } 47 | to { 48 | transform: translateY(30px); 49 | } 50 | } 51 | 52 | .HowWorksInnerRect, 53 | .HowWorksInnerLine { 54 | fill: transparent; 55 | stroke-width: 1px; 56 | stroke-dasharray: 2, 4; 57 | stroke: #222; 58 | } 59 | 60 | .HowWorksRowGroup { 61 | font-size: 10px; 62 | } 63 | 64 | .HowWorksRowRendered, 65 | .HowWorksRowNotRendered, 66 | .HowWorksRowEstimated { 67 | stroke-width: 2px; 68 | stroke: #fff; 69 | } 70 | 71 | .HowWorksRowEstimated { 72 | fill: #ddd; 73 | } 74 | 75 | .HowWorksRowRendered { 76 | fill: #a2d4da; 77 | } 78 | 79 | .HowWorksRowNotRendered { 80 | fill: #aaa; 81 | } 82 | 83 | .HowWorksViewportLine { 84 | stroke-width: 1px; 85 | stroke-dasharray: 2, 4; 86 | stroke: #222; 87 | transition-delay: 1s; 88 | } 89 | 90 | .HowWorksScrollTrack { 91 | fill: rgba(0,0,0,.1); 92 | } 93 | 94 | .HowWorksScrollThumb { 95 | fill: rgba(0,0,0,.5); 96 | } 97 | 98 | .HowWorksScrollingLoop { 99 | animation: scrollingLoop 2.5s linear; 100 | animation-iteration-count: infinite; 101 | } 102 | 103 | @keyframes scrollingLoop { 104 | 0% { transform: translateY(0px); } 105 | 50% { transform: translateY(-40px); } 106 | 100% { transform: translateY(0px); } 107 | } 108 | 109 | .HowWorksScrollingThumbLoop { 110 | animation: scrollingThumbLoop 2.5s linear; 111 | animation-iteration-count: infinite; 112 | } 113 | 114 | @keyframes scrollingThumbLoop { 115 | 0% { transform: translateY(0); } 116 | 50% { transform: translateY(15px); } 117 | 100% { transform: translateY(0); } 118 | } 119 | 120 | .HowWorksTopConditionalScrollingFill { 121 | animation: topConditionalScrollingFill 2.5s linear; 122 | animation-iteration-count: infinite; 123 | } 124 | 125 | @keyframes topConditionalScrollingFill { 126 | 0% { fill: #a2d4da; } 127 | 14% { fill: #a2d4da; } 128 | 15% { fill: #aaa; } 129 | 84% { fill: #aaa; } 130 | 85% { fill: #a2d4da; } 131 | } 132 | 133 | .HowWorksBottomConditionalScrollingFill { 134 | animation: bottomConditionalScrollingFill 2.5s linear; 135 | animation-iteration-count: infinite; 136 | } 137 | 138 | @keyframes bottomConditionalScrollingFill { 139 | 0% { fill: #aaa; } 140 | 14% { fill: #aaa; } 141 | 15% { fill: #a2d4da; } 142 | 84% { fill: #a2d4da; } 143 | 85% { fill: #aaa; } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/Components/HowDoesWindowingWork.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import times from 'lodash.times'; 3 | import React from 'react'; 4 | import CrossHatchRect from './CrossHatchRect'; 5 | import LabeledRect from './LabeledRect'; 6 | import SvgWrapper from './SvgWrapper'; 7 | import './HowDoesWindowingWork.css'; 8 | 9 | export default function HowDoesWindowingWork ({ index }) { 10 | const groupClassName = classnames('HowWorksGroup', { 11 | HowWorksGroupSkewed: index > 2 12 | }) 13 | const innerGroupClassName = classnames('HowWorksHidden', { 14 | HowWorksVisible: index > 1 15 | }) 16 | const innerLinesClassName = classnames('HowWorksInnerLine', { 17 | HowWorksHidden: index > 3, 18 | HowWorksInnerLineAnimated: index > 2 19 | }) 20 | const outerGroupClassName = classnames('HowWorksOuterGroup', 'HowWorksHidden', { 21 | HowWorksVisible: index > 0, 22 | HowWorksOuterRectShifted: index > 2 23 | }) 24 | const rowGroupClassName = classnames('HowWorksRowGroup', 'HowWorksHidden', { 25 | HowWorksVisible: index > 3, 26 | HowWorksScrollingLoop: index >= 5 27 | }) 28 | const viewportLineClassName = classnames('HowWorksViewportLine', 'HowWorksHidden', { 29 | HowWorksVisible: index > 2 30 | }) 31 | const scrollbarClassName = classnames('HowWorksHidden', { 32 | HowWorksVisible: index > 3 33 | }) 34 | const thumbClassName = classnames('HowWorksScrollThumb', { 35 | HowWorksScrollingThumbLoop: index >= 5 36 | }) 37 | const dimmerVisible = index > 3 && index < 5 38 | 39 | return ( 40 | 46 | 47 | 48 | {times(10).map((i) => ( 49 | 50 | ))} 51 | = 4 54 | })} /> 55 | 56 | 57 | {times(10).map((i) => ( 58 | = 5, 66 | HowWorksBottomConditionalScrollingFill: i === 6 && index >= 5, 67 | HowWorksRowNotRendered: i < 2 || i > 5, 68 | HowWorksRowRendered: i >= 2 && i <= 5 69 | })} 70 | textClassName={index >= 5 ? 'HowWorksHidden' : 'HowWorksVisible'} 71 | > 72 | {i >= 2 && i <= 5 73 | ? 'Rendered' 74 | : 'Not Rendered' 75 | } 76 | 77 | ))} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | <ul> 100 | 101 | 102 | 103 | 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /src/Components/LabeledCircle.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React from 'react'; 3 | import './LabeledSvg.css'; 4 | 5 | export default function LabeledCircle ({ 6 | className, 7 | children, 8 | cx, 9 | cy, 10 | hidden, 11 | r, 12 | textClassName 13 | }) { 14 | return ( 15 | 16 | 22 | 33 | 34 | ); 35 | } -------------------------------------------------------------------------------- /src/Components/LabeledRect.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React from 'react'; 3 | import './LabeledSvg.css'; 4 | 5 | export default function LabeledRect ({ 6 | children, 7 | className, 8 | height, 9 | hidden, 10 | mono, 11 | textClassName, 12 | x, 13 | width, 14 | y 15 | }) { 16 | textClassName = classnames('LabeledSvgText', textClassName, { 17 | LabeledSvgTextMono: mono, 18 | NotRenderedLabeledSvgText: hidden 19 | }) 20 | return ( 21 | 22 | 29 | 36 | {children} 37 | 38 | 39 | ); 40 | } -------------------------------------------------------------------------------- /src/Components/LabeledSvg.css: -------------------------------------------------------------------------------- 1 | .LabeledSvgText { 2 | font-size: 14px; 3 | } 4 | 5 | .NotRenderedLabeledSvgText { 6 | font-size: 12px; 7 | } 8 | 9 | .LabeledSvgTextMono { 10 | font-family: monospace; 11 | } -------------------------------------------------------------------------------- /src/Components/MarioSvg.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SvgWrapper from './SvgWrapper'; 3 | 4 | export default function MarioSvg () { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/Components/Note.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const DefaultNote = styled.div` 5 | margin: 0.25rem 0 0.75rem; 6 | background: #4A90E2; 7 | color: #fff; 8 | display: inline-block; 9 | padding: 0.5rem; 10 | border-radius: 0.5rem; 11 | 12 | .fa { 13 | margin-right: 0.25rem; 14 | color: #fff; 15 | } 16 | 17 | a { 18 | color: inherit; 19 | } 20 | ` 21 | 22 | const WarningNote = styled(DefaultNote)` 23 | background: #f2905c; 24 | ` 25 | 26 | export default function Note ({ 27 | children, 28 | type = 'default' 29 | }) { 30 | const Component = type === 'warning' 31 | ? WarningNote 32 | : DefaultNote 33 | 34 | return ( 35 |
36 | 37 | {type === 'default' && ( 38 | 39 | )} 40 | {type === 'warning' && ( 41 | 42 | )} 43 | 44 | {children} 45 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/Components/ResizableRowsList.css: -------------------------------------------------------------------------------- 1 | .ResizableListRow { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | padding: 0 0.5rem; 6 | border-bottom: 1px solid #e9e9e9; 7 | } 8 | 9 | .DragHandle { 10 | position: absolute; 11 | bottom: 0; 12 | left: 0; 13 | right: 0; 14 | height: 16px; 15 | z-index: 2; 16 | cursor: row-resize; 17 | color: rgba(0, 0, 0, 0.2); 18 | } 19 | .DragHandle:hover { 20 | background-color: rgba(0, 0, 0, 0.1); 21 | } 22 | 23 | .DragHandleActive, 24 | .DragHandleActive:hover { 25 | color: rgba(0, 0, 0, 0.6); 26 | z-index: 3; 27 | } 28 | 29 | .DragHandleIcon { 30 | flex: 0 0 12px; 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | align-items: center; 35 | } 36 | -------------------------------------------------------------------------------- /src/Components/ResizableRowsList.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import Draggable from 'react-draggable'; 4 | import { List } from 'react-virtualized'; 5 | import SvgWrapper from './SvgWrapper'; 6 | import './ResizableRowsList.css'; 7 | 8 | export default class ResizableRowsList extends Component { 9 | static propTypes = { 10 | list: PropTypes.array 11 | }; 12 | 13 | constructor (props, context) { 14 | super(props, context); 15 | 16 | this.state = { 17 | rowHeights: Immutable.Map() 18 | }; 19 | 20 | this._rowRenderer = this._rowRenderer.bind(this) 21 | this._rowHeight = this._rowHeight.bind(this) 22 | } 23 | 24 | componentWillUpdate (nextProps, nextState) { 25 | const { rowHeights } = this.state; 26 | 27 | if (rowHeights !== nextState.rowHeights) { 28 | this._list.recomputeRowHeights(0); 29 | } 30 | } 31 | 32 | render () { 33 | const { list } = this.props; 34 | 35 | return ( 36 | this._list = ref} 41 | rowCount={list.length} 42 | rowHeight={this._rowHeight} 43 | rowRenderer={this._rowRenderer} 44 | width={240} 45 | /> 46 | ); 47 | } 48 | 49 | _resizeRow ({ deltaY, index }) { 50 | const { rowHeights } = this.state; 51 | 52 | let rowHeight = this._rowHeight({ index }); 53 | rowHeight = Math.max(40, Math.min(200, rowHeight + deltaY)); 54 | 55 | this.setState({ 56 | rowHeights: rowHeights.set(index, rowHeight) 57 | }); 58 | } 59 | 60 | _rowHeight ({ index }) { 61 | const { rowHeights } = this.state; 62 | 63 | return rowHeights.get(index, 50); 64 | } 65 | 66 | _rowRenderer ({ index, isScrolling, key, style }) { 67 | const { list } = this.props; 68 | 69 | return ( 70 |
75 | {list[index].name} 76 | 77 | this._resizeRow({ 82 | deltaY: data.y, 83 | index: index 84 | })} 85 | position={{ 86 | x: 0, 87 | y: 0 88 | }} 89 | zIndex={999} 90 | > 91 |
92 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
110 |
111 |
112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Components/ScaledList.css: -------------------------------------------------------------------------------- 1 | .BottomGroup, 2 | .MiddleGroup, 3 | .TopGroup { 4 | transition: all 250ms linear; 5 | } 6 | 7 | .MiddleGroup { 8 | position: relative; 9 | z-index: 2; 10 | } 11 | 12 | .BottomGroupScaled { 13 | transform: scaleY(0.5) translateY(-137px); 14 | } 15 | 16 | .MiddleGroupScaled { 17 | transform: translateY(-35px); 18 | } 19 | 20 | .TopGroupScaled { 21 | transform: scaleY(0.5) translateY(-30px); 22 | } 23 | 24 | .OuterScrollContainer { 25 | position: absolute; 26 | top: 5px; 27 | width: 100px; 28 | height: 100px; 29 | border: 4px solid #222; 30 | border-radius: 8px; 31 | } 32 | 33 | .UnscaledListItem, 34 | .UnscaledListItemActive { 35 | display: block; 36 | width: 88px; 37 | height: 26px; 38 | margin: 2px 6px; 39 | } 40 | 41 | .UnscaledListItem { 42 | background-color: #aaa; 43 | } 44 | 45 | .UnscaledListItemActive { 46 | background-color: #C0E0D0; 47 | } 48 | -------------------------------------------------------------------------------- /src/Components/ScaledList.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import times from 'lodash.times'; 3 | import React from 'react'; 4 | import './ScaledList.css'; 5 | 6 | export default ({ scaled = false }) => ( 7 |
8 |
11 | {times(3).map((index) => ( 12 |
13 | ))} 14 |
15 | 16 |
19 | {times(4).map((index) => ( 20 |
21 | ))} 22 | 23 |
24 |
25 | 26 |
29 | {times(5).map((index) => ( 30 |
31 | ))} 32 |
33 |
34 | ); 35 | -------------------------------------------------------------------------------- /src/Components/Scaler.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import { findDOMNode } from 'react-dom'; 3 | import './Slide.css'; 4 | 5 | const WIDTH = 1024; 6 | const HEIGHT = 800; 7 | 8 | export default class Scaler extends Component { 9 | static propTypes = { 10 | children: PropTypes.func.isRequired 11 | }; 12 | 13 | constructor (props, context) { 14 | super(props, context); 15 | 16 | this._onResize = this._onResize.bind(this); 17 | } 18 | 19 | componentDidMount () { 20 | window.addEventListener('resize', this._onResize); 21 | 22 | this._scaleToFit(); 23 | } 24 | 25 | componentWillUnmount () { 26 | window.removeEventListener('resize', this._onResize); 27 | } 28 | 29 | render () { 30 | const { children } = this.props; 31 | 32 | return children ({ 33 | style: this.style 34 | }); 35 | } 36 | 37 | // Inspired by https://github.com/gnab/remark/blob/develop/src/remark/scaler.js 38 | _scaleToFit () { 39 | const node = findDOMNode(document.body); 40 | const { clientHeight, clientWidth } = node; 41 | 42 | let scale; 43 | // let scaledWidth; 44 | let scaledHeight; 45 | let left; 46 | let top; 47 | 48 | if (WIDTH / clientWidth > HEIGHT / clientHeight) { 49 | scale = clientWidth / WIDTH; 50 | } else { 51 | scale = clientHeight / HEIGHT; 52 | } 53 | 54 | // scaledWidth = Math.max(0, clientWidth * scale); 55 | scaledHeight = Math.max(0, clientHeight * scale); 56 | 57 | // left = (clientWidth - scaledWidth); 58 | // top = (clientHeight - scaledHeight); 59 | scale = `scale(${scale})` 60 | 61 | this.style = { 62 | MozTransform: scale, 63 | WebkitTransform: scale, 64 | transform: scale 65 | // margin: `-${top}px -${left}px` 66 | }; 67 | 68 | this.forceUpdate(); 69 | } 70 | 71 | _onResize (event) { 72 | this._scaleToFit(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Components/ScuChart.css: -------------------------------------------------------------------------------- 1 | .scuFalse { 2 | fill: #aaa; 3 | } 4 | 5 | .scuTrue { 6 | fill: #C0E0D0; 7 | } 8 | 9 | .scuAnimatedLine, 10 | .scuFrozenLine { 11 | stroke-width: 1px; 12 | stroke-dasharray: 2, 4; 13 | stroke: #222; 14 | } 15 | .scuAnimatedLine { 16 | animation: dash 500ms linear; 17 | animation-iteration-count: infinite; 18 | } 19 | 20 | @keyframes dash { 21 | from { 22 | stroke-dashoffset: 6; 23 | } 24 | to { 25 | stroke-dashoffset: 0; 26 | } 27 | } 28 | 29 | .scuArrow { 30 | fill: #222; 31 | } 32 | 33 | .scuText { 34 | font-family: monospace; 35 | font-size: 12px; 36 | } -------------------------------------------------------------------------------- /src/Components/ScuChart.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LabeledCircle from './LabeledCircle'; 3 | import SvgWrapper from './SvgWrapper'; 4 | import './ScuChart.css'; 5 | 6 | function Circle ({ label = 'div', scu = false, ...rest }) { 7 | const className = scu 8 | ? 'scuTrue' 9 | : 'scuFalse'; 10 | 11 | return ( 12 | 18 | {label} 19 | 20 | ); 21 | } 22 | 23 | // SVG coordinates 24 | const y0 = 25; 25 | const y1 = 75; 26 | const y3 = 150; 27 | const xRoot = 150; 28 | const xL1a = 75; 29 | const xL1b = 225; 30 | const xL2a = 25; 31 | const xL2b = 125; 32 | const xL2c = 175; 33 | const xL2d = 275; 34 | 35 | export default function Chart ({ step }) { 36 | const allLineClassName = step === 2 37 | ? 'scuAnimatedLine' 38 | : 'scuFrozenLine' 39 | const scuLineClassName = step >= 2 40 | ? 'scuAnimatedLine' 41 | : 'scuFrozenLine' 42 | 43 | const allMarkerClassName = step === 2 44 | ? 'url(#arrow)' 45 | : undefined 46 | const scuMarkerClassName = step >= 2 47 | ? 'url(#arrow)' 48 | : undefined 49 | 50 | return ( 51 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 0} /> 71 | 1 && step < 3} /> 72 | 1 && step < 3} /> 73 | 1 && step < 3} /> 74 | 1} /> 75 | 1} /> 76 | 1 && step < 3} /> 77 | 78 | ); 79 | } -------------------------------------------------------------------------------- /src/Components/Spreadsheet.css: -------------------------------------------------------------------------------- 1 | .GridContainer { 2 | height: 300px; 3 | position: relative; 4 | border: 1px solid #dadada; 5 | overflow: hidden; 6 | } 7 | 8 | .TopLeftCell { 9 | height: 40px; 10 | width: 50px; 11 | background-color: #f3f3f3; 12 | border-bottom: 4px solid #bcbcbc; 13 | border-right: 4px solid #bcbcbc; 14 | } 15 | 16 | .MainGrid { 17 | position: absolute !important; 18 | left: 50px; 19 | top: 40px; 20 | } 21 | 22 | .MainGridCell { 23 | display: flex; 24 | flex-direction: row; 25 | align-items: center; 26 | justify-content: flex-start; 27 | padding: 0.25rem; 28 | outline: 0; 29 | border: none; 30 | border-right: 1px solid #dadada; 31 | border-bottom: 1px solid #dadada; 32 | background-color: #fff; 33 | font-size: 1rem; 34 | } 35 | 36 | .MainGridCellFocused { 37 | box-shadow: 0 0 0 2px #4285FA inset; 38 | } 39 | 40 | .LeftGrid { 41 | position: absolute !important; 42 | left: 0; 43 | top: 40px; 44 | overflow: hidden !important; 45 | } 46 | 47 | .TopGrid { 48 | position: absolute !important; 49 | left: 50px; 50 | top: 0; 51 | height: 40px; 52 | overflow: hidden !important; 53 | } 54 | 55 | .FixedGridCell { 56 | display: flex; 57 | flex-direction: row; 58 | align-items: center; 59 | justify-content: center; 60 | background-color: #f3f3f3; 61 | border-right: 1px solid #ccc; 62 | border-bottom: 1px solid #ccc; 63 | } 64 | 65 | .FixedGridCellFocused { 66 | background-color: #dddddd; 67 | } 68 | -------------------------------------------------------------------------------- /src/Components/Spreadsheet.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React, { Component } from 'react'; 3 | import { AutoSizer, MultiGrid } from 'react-virtualized'; 4 | import './Spreadsheet.css'; 5 | 6 | const LETTERS = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 7 | 8 | export default class MySlide extends Component { 9 | constructor (props, context) { 10 | super(props, context); 11 | 12 | this.state = { 13 | cellValues: {}, 14 | focusedColumnIndex: null, 15 | focusedRowIndex: null 16 | }; 17 | 18 | this._cellRenderer = this._cellRenderer.bind(this); 19 | this._columnWidth = this._columnWidth.bind(this); 20 | this._setRef = this._setRef.bind(this); 21 | } 22 | 23 | componentWillUpdate (nextProps, nextState) { 24 | const { cellValues, focusedColumnIndex, focusedRowIndex } = this.state; 25 | 26 | if ( 27 | focusedColumnIndex !== nextState.focusedColumnIndex || 28 | focusedRowIndex !== nextState.focusedRowIndex 29 | ) { 30 | this._multiGrid.forceUpdate(); 31 | } else if (cellValues !== nextState.cellValues) { 32 | this._multiGrid.forceUpdate(); 33 | } 34 | } 35 | 36 | render () { 37 | return ( 38 | 39 | {({ width }) => ( 40 | 66 | )} 67 | 68 | ); 69 | } 70 | 71 | _cellRenderer ({ columnIndex, key, rowIndex, style }) { 72 | if (columnIndex === 0 && rowIndex === 0) { 73 | return
74 | } else if (columnIndex === 0) { 75 | return this._cellRendererLeft({ columnIndex, key, rowIndex, style }) 76 | } else if (rowIndex === 0) { 77 | return this._cellRendererTop({ columnIndex, key, rowIndex, style }) 78 | } else { 79 | return this._cellRendererMain({ columnIndex, key, rowIndex, style }) 80 | } 81 | } 82 | 83 | _cellRendererLeft ({ columnIndex, key, rowIndex, style }) { 84 | const { focusedRowIndex } = this.state; 85 | 86 | return ( 87 |
94 | {rowIndex} 95 |
96 | ); 97 | } 98 | 99 | _cellRendererMain ({ columnIndex, key, rowIndex, style }) { 100 | const { cellValues, focusedColumnIndex, focusedRowIndex } = this.state; 101 | 102 | const value = cellValues[key] || ''; 103 | 104 | const isFocused = ( 105 | columnIndex === focusedColumnIndex && 106 | rowIndex === focusedRowIndex 107 | ); 108 | 109 | return ( 110 | { 116 | this.setState({ 117 | cellValues: { 118 | ...cellValues, 119 | [key]: event.target.value 120 | } 121 | }) 122 | }} 123 | onFocus={() => this.setState({ 124 | focusedColumnIndex: columnIndex, 125 | focusedRowIndex: rowIndex 126 | })} 127 | placeholder={`cell ${rowIndex}, ${columnIndex}`} 128 | style={style} 129 | value={value} 130 | /> 131 | ); 132 | } 133 | 134 | _cellRendererTop ({ columnIndex, key, rowIndex, style }) { 135 | const { focusedColumnIndex } = this.state; 136 | 137 | return ( 138 |
145 | {LETTERS[columnIndex]} 146 |
147 | ); 148 | } 149 | 150 | _columnWidth ({ index }) { 151 | return index === 0 ? 40 : 100; 152 | } 153 | 154 | _setRef (ref) { 155 | this._multiGrid = ref; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Components/StyledListElements.js: -------------------------------------------------------------------------------- 1 | import { List as ReactVirtualizedList } from 'react-virtualized'; 2 | import styled from 'styled-components'; 3 | 4 | export const VirtualList = styled(ReactVirtualizedList)` 5 | width: 15rem; 6 | height: 15rem; 7 | overflow: auto; 8 | font-size: 12px; 9 | border: 1px solid #CFD8DC; 10 | margin: 1rem 0; 11 | `; 12 | 13 | export const List = styled.div` 14 | width: 15rem; 15 | height: 15rem; 16 | overflow: auto; 17 | font-size: 12px; 18 | border: 1px solid #CFD8DC; 19 | margin: 1rem 0; 20 | `; 21 | 22 | export const ListWithBorderRadius = styled(List)` 23 | border-radius: 1rem; 24 | border-width: 4px; 25 | overflow: auto; 26 | `; 27 | 28 | export const ListRow = styled.div` 29 | padding: 0 0.5rem; 30 | width: 100%; 31 | height: 40px; 32 | line-height: 40px; 33 | transition: 0 all; 34 | white-space: pre; 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | display: flex; 38 | flex-direction: row; 39 | align-items: center; 40 | border-bottom: 1px solid #e9e9e9; 41 | `; 42 | 43 | export const ListRowActive = styled(ListRow)` 44 | background-color: #e9e9e9; 45 | `; 46 | 47 | export const RowNumber = styled.div` 48 | flex: 0 0 2rem; 49 | display: inline-block; 50 | width: 2rem; 51 | height: 2rem; 52 | line-height: 2rem; 53 | border-radius: 2rem; 54 | text-align: center; 55 | color: #fff; 56 | `; 57 | 58 | export const RowStack = styled.div` 59 | flex: 1 0 5.25rem; 60 | margin: 0 0.5rem; 61 | font-size: 20px; 62 | `; 63 | 64 | export const RowName = styled.div` 65 | font-size: .8rem; 66 | line-height: .8rem; 67 | font-weight: bold; 68 | `; 69 | 70 | export const RowSummary = styled.div` 71 | font-size: 0.6rem; 72 | line-height: 0.6rem; 73 | margin-top: 0.25rem; 74 | `; 75 | 76 | export const RowStar = styled.div` 77 | flex: 0 0 auto; 78 | font-size: 1rem !important; 79 | color: #FF502F !important; 80 | `; 81 | 82 | export const ListScrolling = styled.div` 83 | font-style: italic; 84 | opacity: 0.5; 85 | `; 86 | -------------------------------------------------------------------------------- /src/Components/SvgWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function SvgWrapper ({ viewBoxHeight, viewBoxWidth, ...rest }) { 4 | return ( 5 | 10 | ); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Components/TreasureDataIcon.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvaughn/forward-js-2017/d871899f8a4805ab325cf8c51cdcf5676f5360fc/src/Components/TreasureDataIcon.js -------------------------------------------------------------------------------- /src/ForwardTheme.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { injectGlobal } from 'styled-components' 3 | 4 | const globalStyles = ` 5 | html, body { 6 | height: 100%; 7 | box-sizing: border-box; 8 | margin: 0; 9 | padding: 0; 10 | } 11 | * { 12 | box-sizing: inherit; 13 | } 14 | 15 | body { 16 | font-family: 'Roboto', sans-serif; 17 | font-size: 20px; 18 | } 19 | h1, h2, h3, h4 { 20 | font-weight: 400; 21 | margin: 0 0 1rem; 22 | } 23 | h1 { 24 | font-size: 2.5rem; 25 | } 26 | h2 { 27 | font-size: 1.75rem; 28 | } 29 | h3 { 30 | font-size: 1.5rem; 31 | } 32 | h4 { 33 | font-size: 1rem; 34 | } 35 | 36 | li { 37 | margin: 0 0 0.5rem; 38 | } 39 | 40 | html, body, #root { 41 | height: 100%; 42 | } 43 | 44 | a { 45 | color: #FF502F; 46 | text-decoration: none; 47 | } 48 | 49 | p { 50 | margin: 0 0 1rem; 51 | } 52 | 53 | code { 54 | background: rgba(0,0,0,.05); 55 | border-radius: 5px; 56 | font-family: monospace; 57 | } 58 | 59 | strike { 60 | color: #666; 61 | } 62 | 63 | button { 64 | padding: 0.5rem 1rem; 65 | background-color: #FF502F; 66 | border: 0.1em solid #FFFFFF; 67 | border-radius: 0; 68 | color: #fff; 69 | font-weight: 400; 70 | font-size: 20px; 71 | cursor: pointer; 72 | } 73 | 74 | button:disabled { 75 | opacity: 0.25; 76 | cursor: default; 77 | } 78 | 79 | .CodeMirror-scroll { 80 | overflow: hidden !important; 81 | } 82 | 83 | .VirtualizedSelect { 84 | font-size: 14px; 85 | } 86 | ` 87 | 88 | export default class ForwardTheme extends Component { 89 | componentWillMount () { 90 | injectGlobal([`${globalStyles}`]) 91 | } 92 | 93 | render () { 94 | return ( 95 | 100 | ) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Presentation/ContentSlide.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import presenterSlideStyle from './presenterSlideStyle' 3 | 4 | export default styled.div` 5 | height: 100%; 6 | padding: 1rem; 7 | ${presenterSlideStyle} 8 | @media (max-width: 600px) { 9 | padding: 0.5rem; 10 | } 11 | h1 { 12 | paddingRight: 9rem; 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | color: #444; 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /src/Presentation/TitleSlide.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import presenterSlideStyle from './presenterSlideStyle' 3 | 4 | export default styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | text-align: center; 10 | height: 100%; 11 | background-color: #000; 12 | color: #fff; 13 | padding: 1rem; 14 | ${presenterSlideStyle} 15 | @media (max-width: 600px) { 16 | padding: 0.5rem; 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /src/Presentation/presenterSlideStyle.js: -------------------------------------------------------------------------------- 1 | export default (props) => props.theme.isPresenterMode && ` 2 | outline: 0.25rem solid #37F; 3 | outline-offset: -0.25rem; 4 | &::before { 5 | content: 'presenter'; 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | background-color: #36F; 10 | padding: 0.25rem; 11 | color: #fff; 12 | font-size: 0.65rem; 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /src/Slides/000.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | import styled from 'styled-components'; 4 | 5 | const Container = styled.p` 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: center; 10 | `; 11 | 12 | const Spacer = styled.span` 13 | width: 3rem; 14 | `; 15 | 16 | const Icon = styled.i` 17 | font-size: 2rem !important; 18 | line-height: 2rem !important; 19 | margin: 0 0.5rem; 20 | color: #ccc; 21 | `; 22 | 23 | const slide = () => ( 24 | 25 |

{slide.title}

26 |

27 | github.com/bvaughn/forward-js-2017 28 |

29 | 30 | @bvaughn 31 | 32 | @brian_d_vaughn 33 | 34 |
35 | ); 36 | 37 | slide.title = 'Creating more efficient React views with windowing' 38 | 39 | export default slide; 40 | -------------------------------------------------------------------------------- /src/Slides/001.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | 6 | const HeartIcon = styled.i` 7 | color: #f92672 !important; 8 | `; 9 | const FacebookIcon = styled.i` 10 | color: #3b5998 !important; 11 | `; 12 | const GithubIcon = styled.i` 13 | color: #222 !important; 14 | `; 15 | 16 | const slide = () => ( 17 | 18 |

{slide.title}

19 |
    20 | 21 |
  • 22 | Facebook + React 23 |
  • 24 |
    25 | 26 |
  • 27 | github.com/bvaughn 28 |
  • 29 |
    30 | 31 |
  • 32 | performance, search, and app architecture 33 |
  • 34 |
    35 |
36 |
37 | ); 38 | 39 | slide.title = 'Who am I?'; 40 | 41 | export default slide; 42 | -------------------------------------------------------------------------------- /src/Slides/002.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | 5 | const slide = () => ( 6 | 7 |

{slide.title}

8 |

To talk about performance.

9 |
    10 |
  • What slows React applications down?
  • 11 |
  • How can we fix?
  • 12 |
  • Share some lessons-learned
  • 13 |
14 |
15 | ); 16 | 17 | slide.title = 'Why are we here?'; 18 | 19 | export default slide; 20 | -------------------------------------------------------------------------------- /src/Slides/003.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import image from '../../public/computer-guy.png'; 5 | 6 | const slide = () => ( 7 | 8 |

{slide.title}

9 | 10 | 16 | 17 |
    18 |
  • Older hardware
  • 19 |
  • Load times
  • 20 |
  • Scrolling UX for mobile
  • 21 |
  • Battery life
  • 22 |
23 |
24 | ); 25 | 26 | slide.title = 'Why does performance matter?'; 27 | 28 | export default slide; 29 | -------------------------------------------------------------------------------- /src/Slides/004.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | 6 | const SectionCheck = styled.i` 7 | margin-left: 0.5rem; 8 | color: #C0E0D0 !important; 9 | ` 10 | 11 | const slide = () => ( 12 | 13 |

{slide.title}

14 | 15 | 16 |

17 | Browser 18 |

19 |
20 | 21 | 22 |
    23 |
  • DOM elements and mutations
  • 24 |
  • Repaints and reflows
  • 25 |
  • Garbage collection
  • 26 |
27 |
28 | 29 | 30 |

31 | React 32 |

33 |
34 | 35 |
    36 |
  • Unnecessary renders
  • 37 |
  • Development build of React 🤡
  • 38 |
39 |
40 |
41 | ); 42 | 43 | slide.title = 'What can slow a React app down?'; 44 | 45 | export default slide; 46 | -------------------------------------------------------------------------------- /src/Slides/100.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

{slide.title}

7 |
8 | ); 9 | 10 | slide.title = 'General React tips' 11 | 12 | export default slide; 13 | -------------------------------------------------------------------------------- /src/Slides/101.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | 5 | const slide = () => ( 6 | 7 |

{slide.title}

8 | 9 | 13 |
14 | ); 15 | 16 | slide.title = 'Use the production build of React'; 17 | 18 | export default slide; 19 | -------------------------------------------------------------------------------- /src/Slides/102.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import styled from 'styled-components'; 4 | import ContentSlide from '../Presentation/ContentSlide'; 5 | import ScuChart from '../Components/ScuChart'; 6 | 7 | const Spacer = styled.div` 8 | margin-top: 0.5rem; 9 | ` 10 | 11 | const sourceA = require('raw!../../examples/pure-component.js'); 12 | const sourceB = require('raw!../../examples/shallow-compare.js'); 13 | 14 | const slide = ({ stepIndex }) => ( 15 | 16 |

{slide.title}

17 | 18 | 19 | 20 | 21 | 22 | 23 |
Use shouldComponentUpdate
24 | 25 | 26 |
27 |

28 | React 15.3+ use PureComponent 29 |

30 |
    31 |
  • Compares current props and state to next
  • 32 |
  • Only render when changes
  • 33 |
34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |

43 | React < 15.3 use shallowCompare 44 |

45 | 46 | 47 | 48 |
49 |
50 | 51 |
52 | ); 53 | 54 | slide.title = 'Avoid unnecessary renders'; 55 | 56 | export default slide; 57 | -------------------------------------------------------------------------------- /src/Slides/103.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import styled from 'styled-components'; 4 | import ContentSlide from '../Presentation/ContentSlide'; 5 | 6 | const Badge = styled.span` 7 | padding: 6px 8px; 8 | background-color: #4285f4; 9 | color: #fff; 10 | box-shadow: 0 1px 4px 0 rgba(0,0,0,0.14); 11 | border-radius: 4px; 12 | ` 13 | 14 | const slide = ({ stepIndex }) => ( 15 | 16 |

{slide.title}

17 | 18 |
    19 | 20 |
    21 |

    22 | User badge:  23 | Brian Vaughn (brian.david.vaughn@gmail.com) 24 |

    25 | 26 |
      27 |
    • 🙁 users (array), index (number)
    • 28 |
    • 🙂 user (object)
    • 29 |
    • 😁 name (string), email (string)
    • 30 |
    • Simplifies testing!
    • 31 |
    32 |
    33 |
    34 | 35 |
  • 36 | Immutable data 37 | 38 |
      39 |
    • Mutation creates new instance
    • 40 |
    • Faster change detection
    • 41 |
    42 |
  • 43 |
    44 |
45 |
46 | ); 47 | 48 | slide.title = 'Choose props carefully'; 49 | 50 | export default slide; 51 | -------------------------------------------------------------------------------- /src/Slides/104.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Code, Step } from 'react-presents'; 4 | import ContentSlide from '../Presentation/ContentSlide'; 5 | 6 | const sourceA = require('raw!../../examples/function-children-example-2.js'); 7 | const sourceB = require('raw!../../examples/function-children-example-1.js'); 8 | const sourceC = require('raw!../../examples/function-children-example-3.js'); 9 | const sourceD = require('raw!../../examples/element-child.js'); 10 | const sourceE = require('raw!../../examples/function-child.js'); 11 | 12 | const DIM_LINES = [ 13 | undefined, 14 | [[0,1], [4,10], [13,14]], 15 | [[0,3], [6,8], [11,14]], 16 | [[0,5], [9,14]] 17 | ] 18 | 19 | const Spacer = styled.div` 20 | margin-top: 0.5rem; 21 | ` 22 | 23 | const slide = ({ stepIndex }) => ( 24 | 25 |

{slide.title}

26 | 27 | 28 |
29 |

Element child

30 | 31 | 32 |
33 | 34 |

Function child

35 | 36 |
37 |
38 | 39 |

40 | 41 | ...but why? 42 |

43 |
44 |
45 |
46 | 47 | 48 |

Let's say our app is localized...

49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 |

Let's say we have a session...

57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 |

Now we can compose them...

65 |
66 | 67 | 68 | 72 | 73 |
74 | ); 75 | 76 | slide.title = 'Render callbacks (aka function children)'; 77 | 78 | export default slide; 79 | -------------------------------------------------------------------------------- /src/Slides/200.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

{slide.title}

7 |
8 | ); 9 | 10 | slide.title = 'What about too many DOM elements, GC, etc?' 11 | 12 | export default slide; 13 | -------------------------------------------------------------------------------- /src/Slides/201.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import MarioSvg from '../Components/MarioSvg'; 6 | import kindleImage from '../../public/kindle.jpg'; 7 | import occlusionImage from '../../public/occlusion-culling.jpg'; 8 | 9 | const OcclusionImage = styled.img` 10 | width: 300px; 11 | max-width: 100%; 12 | height: auto; 13 | ` 14 | 15 | const KindleImage = styled.img` 16 | width: 250px; 17 | max-width: 100%; 18 | height: auto; 19 | margin-left: 2.5rem; 20 | ` 21 | 22 | const Spacer = styled.div` 23 | margin-top: 0.5rem; 24 | ` 25 | 26 | const slide = () => ( 27 | 28 |

{slide.title}

29 | 30 | 31 |
32 |
    33 |
  • Known as "windowing".
  • 34 |
  • Analog example: books Kindle
  • 35 |
36 | 37 | 38 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |

"Occlusion culling" for video games

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 |
    64 |
  • Given a perspective of a user
  • 65 |
  • Which objects are visible?
  • 66 |
67 |
68 |
69 | 70 | 71 |
72 |

"Windowing" for web and mobile

73 | 74 |
    75 |
  • Given a small list (eg <ul>, UITableView)
  • 76 |
  • And large set of items (eg <li>, UITableViewCell)
  • 77 |
  • Which items are visible?
  • 78 |
79 |
80 |
81 |
82 | ); 83 | 84 | slide.title = 'Only create elements the user can see' 85 | 86 | export default slide; 87 | -------------------------------------------------------------------------------- /src/Slides/202.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import HowDoesWindowingWork from '../Components/HowDoesWindowingWork'; 6 | 7 | const HiddenLi = styled.li` 8 | opacity: 0; 9 | `; 10 | 11 | function MaybeLI ({ children, index, stepIndex }) { 12 | return stepIndex >= index 13 | ? ( 14 |
  • {children}
  • 15 | ) : ( 16 | {children} 17 | ); 18 | } 19 | 20 | const title = 'How does windowing work?'; 21 | 22 | const slide = ({ stepIndex }, { slide }) => { 23 | slide.setNumSteps(6); 24 | 25 | return ( 26 | 27 |

    {title}

    28 | 29 |
      30 | 31 |
      32 | Small DOM element (eg <ul>) 33 |
      34 |
      35 | 36 |
      37 | Items 38 |
      39 |
      40 | 41 |
      42 | Big DOM element for scrolling 43 |
      44 |
      45 | 46 |
      47 | Absolutely positioned visible rows 48 |
      49 |
      50 |
    51 | 52 | 53 |
    54 | ); 55 | } 56 | 57 | slide.contextTypes = { 58 | slide: PropTypes.any 59 | }; 60 | slide.title = title; 61 | 62 | export default slide; 63 | -------------------------------------------------------------------------------- /src/Slides/203.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import calendarGif from '../../public/how-is-it-used-calendar.gif'; 6 | import dropDownGif from '../../public/how-is-it-used-dropdown.gif'; 7 | import tableGif from '../../public/how-is-it-used-table.gif'; 8 | import treeGif from '../../public/how-is-it-used-tree.gif'; 9 | 10 | const ImageContainer = styled.i` 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | ` 15 | 16 | const Image = styled.img` 17 | margin-top: 1rem; 18 | border: 1px solid #ddd; 19 | ` 20 | 21 | const slide = () => ( 22 | 23 |

    {slide.title}

    24 | 25 | 26 | 45 | 46 | 47 | 48 |

    Key question: Will the data grow?

    49 |
    50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
    66 | ); 67 | 68 | slide.title = 'What can windowing be used for?'; 69 | 70 | export default slide; 71 | -------------------------------------------------------------------------------- /src/Slides/204.js: -------------------------------------------------------------------------------- 1 | import now from 'performance-now'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import Note from '../Components/Note'; 5 | import { List, ListRow, RowName, RowNumber, RowStack, RowSummary } from '../Components/StyledListElements'; 6 | 7 | export default class Slide extends Component { 8 | static contextTypes = { 9 | list: PropTypes.array 10 | }; 11 | 12 | static title = 'How much can windowing improve performance?'; 13 | 14 | constructor (props, context) { 15 | super(props, context); 16 | 17 | this.state = { 18 | initializationTime: 0, 19 | initialized: false 20 | }; 21 | } 22 | 23 | componentDidUpdate (prevProps, prevState) { 24 | const { initialized } = this.state; 25 | 26 | if (initialized && !prevState.initialized) { 27 | window.requestIdleCallback(() => { 28 | this.setState({ 29 | initializationTime: now() - this._initializationStartedAt 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | componentWillUpdate (nextProps, nextState) { 36 | const { initialized } = this.state; 37 | 38 | if (nextState.initialized && !initialized) { 39 | this._initializationStartedAt = now(); 40 | } 41 | } 42 | 43 | render () { 44 | const { list } = this.context; 45 | const { initializationTime, initialized } = this.state; 46 | 47 | const ListComponent = List; 48 | 49 | return ( 50 | 51 |

    {Slide.title}

    52 |

    Rendering a big list with React

    53 | {initialized || ( 54 | 57 | )} 58 | {initialized && ( 59 |
    60 | {initializationTime === 0 && ( 61 | 62 | Measuring ... 63 | 64 | )} 65 | {initializationTime > 0 && ( 66 | 67 | Created list in {Math.round(initializationTime) / 1e3} seconds 68 | 69 | )} 70 | 71 | 72 | {list.map((item, index) => ( 73 | 74 | 79 | {item.name.substr(0, 1)} 80 | 81 | 82 | {item.name} 83 | This is row {index} 84 | 85 | 86 | ))} 87 | 88 | 89 | 90 | Scrolling performance also poor if repaints are triggered (eg border-radius) 91 | 92 |
    93 | )} 94 |
    95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Slides/205.js: -------------------------------------------------------------------------------- 1 | import now from 'performance-now'; 2 | import numeral from 'numeral'; 3 | import React, { Component, PropTypes } from 'react'; 4 | import ContentSlide from '../Presentation/ContentSlide'; 5 | import ExampleList from '../Components/ExampleList'; 6 | import Note from '../Components/Note'; 7 | 8 | export default class Slide extends Component { 9 | static contextTypes = { 10 | list: PropTypes.array 11 | }; 12 | 13 | static title = 'How much can windowing improve performance?'; 14 | 15 | constructor (props, context) { 16 | super(props, context); 17 | 18 | this.state = { 19 | formattedListSize: numeral(context.list.length).format(), 20 | initializationTime: 0, 21 | initialized: false 22 | }; 23 | } 24 | 25 | componentDidUpdate (prevProps, prevState) { 26 | const { initialized } = this.state; 27 | 28 | if (initialized && !prevState.initialized) { 29 | window.requestIdleCallback(() => { 30 | this.setState({ 31 | initializationTime: now() - this._initializationStartedAt 32 | }); 33 | }); 34 | } 35 | } 36 | 37 | componentWillUpdate (nextProps, nextState) { 38 | const { initialized } = this.state; 39 | 40 | if (nextState.initialized && !initialized) { 41 | this._initializationStartedAt = now(); 42 | } 43 | } 44 | 45 | render () { 46 | const { formattedListSize, initializationTime, initialized } = this.state; 47 | 48 | return ( 49 | 50 |

    {Slide.title}

    51 |

    Rendering the same list with windowing

    52 | {initialized || ( 53 | 56 | )} 57 | {initialized && ( 58 |
    59 | {initializationTime === 0 && ( 60 | 61 | Creating List ... 62 | 63 | )} 64 | {initializationTime > 0 && ( 65 | 66 | Created list in {Math.round(initializationTime) / 1e3} seconds ({formattedListSize} items) 67 | 68 | )} 69 | 70 | 71 | 72 | 73 | Open the browser Timeline view and scroll 74 | 75 |
    76 | )} 77 |
    78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Slides/206.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | 5 | const sourceNonVirtualized = require('raw!../../examples/perf-comparison-non-virtualized.js'); 6 | const sourceVirtualized = require('raw!../../examples/perf-comparison-virtualized.js'); 7 | 8 | const slide = () => ( 9 | 10 |

    {slide.title}

    11 | 12 |
    13 |

    Rendering a list with React

    14 | 15 |
    16 |
    17 | 18 |
    19 |

    Rendering a list with React

    20 | 24 |
    25 |
    26 | 27 |
    28 |

    Rendering a list with React

    29 | 33 |
    34 |
    35 | 36 |
    37 |

    Rendering a list with react-virtualized

    38 | 39 |
    40 |
    41 | 42 |
    43 |

    Rendering a list with react-virtualized

    44 | 48 |
    49 |
    50 | 51 |
    52 |

    Rendering a list with react-virtualized

    53 | 57 |
    58 |
    59 |
    60 | ); 61 | 62 | slide.title = 'How complicated is it to use windowing?'; 63 | 64 | export default slide; 65 | -------------------------------------------------------------------------------- /src/Slides/300.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

    {slide.title}

    7 |

    github.com/bvaughn/react-virtualized

    8 |
    9 | ); 10 | 11 | slide.title = 'react-virtualized' 12 | 13 | export default slide; 14 | -------------------------------------------------------------------------------- /src/Slides/301.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import { CollectionSvg, GridSvg, ListSvg, TableSvg } from '../Components/BuildingBlocks'; 5 | 6 | const slide = () => ( 7 | 8 |

    {slide.title}

    9 | 10 | 11 |
    12 |

    List

    13 | 14 |
    15 |
    16 | 17 |
    18 |

    Table

    19 | 20 |
    21 |
    22 | 23 |
    24 |

    Grid

    25 | 26 |
    27 |
    28 | 29 |
    30 |

    Collection

    31 | 32 |
    33 |
    34 |
    35 | ); 36 | 37 | slide.title = 'The building blocks'; 38 | 39 | export default slide; 40 | -------------------------------------------------------------------------------- /src/Slides/400.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

    {slide.title}

    7 |
    8 | ); 9 | 10 | slide.title = 'Deeper dive' 11 | 12 | export default slide; 13 | -------------------------------------------------------------------------------- /src/Slides/401.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import chatImage from '../../public/non-uniform-heights-chat.png'; 5 | import dropDownImage from '../../public/non-uniform-heights-drop-down.png'; 6 | 7 | const sourceRowHeightGetter = require('raw!../../examples/dynamic-row-height-getter.js'); 8 | const sourceCellMeasurer = require('raw!../../examples/dynamic-cell-measurer.js'); 9 | 10 | const CELL_MEASURER_LINES = [ 11 | [], 12 | [[0,1], [3,10], [12,27]], 13 | [[0,3], [11,11], [13,21], [23,27]], 14 | [[1,4], [6,19], [22,27]] 15 | ] 16 | 17 | const slide = ({ stepIndex }) => ( 18 | 19 |

    {slide.title}

    20 | 21 | 22 |
    23 |

    This introduces a couple of challenges:

    24 |
      25 |
    • Total size calculation
    • 26 |
    • Initial performance
    • 27 |
    28 |
    29 |
    30 | 31 | 32 |
    33 |

    eg Size can be inferred from data

    34 | 35 |
    36 |
    37 | 38 | 39 | 43 | 44 | 45 | 46 |
    47 |

    eg Must be measured by browser

    48 | 49 |
    50 |
    51 | 52 | 53 | 57 | 58 |
    59 | ); 60 | 61 | slide.title = 'What about variable sized content?'; 62 | 63 | export default slide; 64 | -------------------------------------------------------------------------------- /src/Slides/402.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import DeferMeasurements from '../Components/DeferMeasurements'; 6 | 7 | const Spacer = styled.div` 8 | margin-top: 0.5rem; 9 | ` 10 | 11 | const slide = () => ( 12 | 13 |

    {slide.title}

    14 |
      15 |
    • Yes! but...
    • 16 |
    17 | 18 | 19 | 20 | 21 |
    22 | ); 23 | 24 | slide.title = 'Is measuring expensive?'; 25 | 26 | export default slide; 27 | -------------------------------------------------------------------------------- /src/Slides/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import image from '../../public/cache-all-the-things.png'; 6 | 7 | const Image = styled.img` 8 | margin-left: 0.5rem; 9 | `; 10 | 11 | const slide = () => ( 12 | 13 |

    {slide.title}

    14 | 15 | 16 |
    17 |
      18 |
    • Measurements?
    • 19 |
    • Cells?
    • 20 | 21 |
    • 22 | 28 |
    • 29 |
      30 |
    31 |
    32 |
    33 | 34 | 35 |
    36 |

    37 | Stateful views are tricky. 38 |

    39 | 40 |
      41 | 42 |
    • Cache while scrolling
    • 43 |
      44 | 45 |
    • Clear cache after
    • 46 |
      47 |
    48 |
    49 |
    50 |
    51 | ); 52 | 53 | slide.title = 'What can be cached?'; 54 | 55 | export default slide; 56 | -------------------------------------------------------------------------------- /src/Slides/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import styled from 'styled-components'; 5 | import AnimatedList from '../Components/AnimatedList'; 6 | import ExampleList from '../Components/ExampleList'; 7 | import Note from '../Components/Note'; 8 | import movie from '../../public/overscan-example.mp4'; 9 | 10 | const Video = styled.video` 11 | border: 1px solid #ddd; 12 | border-radius: 0.25rem; 13 | ` 14 | 15 | const slide = () => ( 16 | 17 |

    {slide.title}

    18 | 19 | 20 |
    21 | 22 | 33 | 34 | Unique to windowing. 35 |
    36 |
    37 | 38 | 39 |
    40 |

    Why does windowing cause this?

    41 |
      42 | 43 |
    • Separate thread
    • 44 |
      45 | 46 |
    • JavaScript updated periodically
    • 47 |
      48 | 49 |
    • Sometimes JS isn't fast enough (16ms frame budget)
    • 50 |
      51 |
    52 |
    53 |
    54 | 55 | 56 |
    57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 | 66 | 67 | 68 | View source to see overscanned rows 69 | 70 |
    71 |
    72 |
    73 |
    74 |
    75 | ); 76 | 77 | slide.title = 'Will scrolling still feel natural?'; 78 | 79 | export default slide; 80 | -------------------------------------------------------------------------------- /src/Slides/405.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import ScaledList from '../Components/ScaledList'; 5 | import image from '../../public/browser-limits-cutoff.png'; 6 | 7 | const slide = ({ stepIndex }) => { 8 | return ( 9 | 10 |

    {slide.title}

    11 | 12 | 13 |
    14 | 15 |

    16 | DOM element size limits (eg Chrome 33.5M px, IE 1.5M px) 17 |

    18 |
    19 |
      20 |
    • Browser won't render past
    • 21 |
    • Can't scroll past
    • 22 |
    • Layout gets wonky
    • 23 |
    24 | 25 | 31 | 32 |
    33 |
    34 | 35 | 36 |
    37 |

    Compression things

    38 | 39 | 8} /> 40 | 41 |
    42 |
    43 |
    44 | ); 45 | } 46 | 47 | slide.title = 'Will we run into browser limitations?'; 48 | 49 | export default slide; 50 | -------------------------------------------------------------------------------- /src/Slides/406.js: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React, { Component, PropTypes } from 'react'; 3 | import { Code, Step } from 'react-presents'; 4 | import ContentSlide from '../Presentation/ContentSlide'; 5 | import { List } from 'react-virtualized'; 6 | import styled from 'styled-components'; 7 | import Note from '../Components/Note'; 8 | import image from '../../public/profile-picture.jpg'; 9 | 10 | const source = require('raw!../../examples/is-scrolling-cell-renderer.js').trim(); 11 | 12 | const Image = styled.img` 13 | width: 60px; 14 | height: 60px; 15 | `; 16 | 17 | const ImageContainer = styled.div` 18 | position: relative; 19 | width: 60px; 20 | height: 60px; 21 | margin-right: 0.5rem; 22 | ` 23 | 24 | const ImageTinter = styled.div` 25 | content: ""; 26 | display: block; 27 | position: absolute; 28 | top: 0; 29 | bottom: 0; 30 | left: 0; 31 | right: 0; 32 | ` 33 | 34 | const ImagePlaceholder = styled.div` 35 | width: 60px; 36 | height: 60px; 37 | background-color: #aaa; 38 | border-radius: 0.5rem; 39 | margin-right: 0.5rem; 40 | ` 41 | 42 | const ImageListRowText = styled.span` 43 | font-size: 1rem; 44 | ` 45 | 46 | export default class Slide extends Component { 47 | static contextTypes = { 48 | list: PropTypes.array 49 | }; 50 | 51 | static title = 'What about elements that are really complex?'; 52 | 53 | render () { 54 | const { list } = this.context; 55 | 56 | return ( 57 | 58 |

    {Slide.title}

    59 | 60 | 61 |
    62 |
      63 |
    • <canvas> or <svg>
    • 64 |
    • Images (trigger network requests)
    • 65 |
    66 |
    67 |
    68 | 69 | 70 |
    71 |

    Render less while scrolling.

    72 | 73 | 74 | 75 | 76 |
    77 |
    78 | 79 | 80 |
    81 | ( 88 |
    96 | {isScrolling && ( 97 | 98 | )} 99 | {!isScrolling && ( 100 | 101 | 105 | 110 | 111 | )} 112 | 113 | {list[index].name} 114 | 115 |
    116 | )} 117 | width={240} 118 | /> 119 | 120 | 121 | Scroll to see row renderer changes. 122 | 123 |
    124 |
    125 |
    126 | ); 127 | } 128 | } 129 | 130 | function hexToRgba(hex, alpha) { 131 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 132 | const r = parseInt(result[1], 16); 133 | const g = parseInt(result[2], 16); 134 | const b = parseInt(result[3], 16); 135 | return `rgba(${r}, ${g}, ${b}, ${alpha})` 136 | } 137 | -------------------------------------------------------------------------------- /src/Slides/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

    {slide.title}

    7 |
    8 | ); 9 | 10 | slide.title = 'Examples & recipes' 11 | 12 | export default slide; 13 | -------------------------------------------------------------------------------- /src/Slides/501.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import Note from '../Components/Note'; 5 | import DragAndDropList from '../Components/DragAndDropList'; 6 | 7 | const source = require('raw!../../examples/drag-and-drop-key-points.js'); 8 | 9 | const DIM_LINES = [ 10 | [[1,3], [6,27]], 11 | [[0,9], [13,18], [26,27]], 12 | [[0,20], [24,27]] 13 | ] 14 | 15 | export default class Slide extends Component { 16 | static contextTypes = { 17 | list: PropTypes.array 18 | }; 19 | 20 | static propTypes = { 21 | stepIndex: PropTypes.number.isRequired 22 | }; 23 | 24 | static title = 'Drag-and-drop rows'; 25 | 26 | render () { 27 | const { list } = this.context; 28 | const { stepIndex } = this.props; 29 | 30 | return ( 31 | 32 |

    {Slide.title}

    33 | 34 | 35 |
    36 |

    37 | Use react-sortable-hoc with react-virtualized for drag-and-drop. 38 |

    39 | 40 | 41 |
    42 | 43 | Click and drag rows above 44 |
    45 |
    46 |
    47 |
    48 | 49 | 50 | 54 | 55 |
    56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Slides/502.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import ResizableRowsList from '../Components/ResizableRowsList'; 5 | 6 | const source = require('raw!../../examples/resizable-cells-key-points.js'); 7 | 8 | const DIM_LINES = [ 9 | [[1,10], [17,29]] 10 | ] 11 | 12 | const slide = ({ stepIndex }, { list }) => ( 13 | 14 |

    {slide.title}

    15 | 16 | 17 |
    18 |

    19 | Use react-draggable with react-virtualized for resizable rows. 20 |

    21 | 22 | 23 | 24 | 25 |
    26 |
    27 | 28 | 29 | 33 | 34 |
    35 | ); 36 | 37 | slide.contextTypes = { 38 | list: PropTypes.array.isRequired 39 | }; 40 | 41 | slide.title = 'Resizable rows'; 42 | 43 | export default slide; 44 | -------------------------------------------------------------------------------- /src/Slides/503.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import Note from '../Components/Note'; 5 | import Spreadsheet from '../Components/Spreadsheet'; 6 | 7 | const sourceGrids = require('raw!../../examples/multi-grid.js'); 8 | const sourceRenderer = require('raw!../../examples/multi-grid-renderers.js'); 9 | 10 | const GRID_DIM_LINES = [ 11 | undefined, 12 | [[0,5], [8,14]] 13 | ]; 14 | 15 | const RENDERER_DIM_LINES = [ 16 | undefined, 17 | [[0,1], [4,6], [8,8], [10,11]], 18 | [[0,3], [7,7], [9,11]] 19 | ]; 20 | 21 | const slide = ({ stepIndex }, { list }) => { 22 | return ( 23 | 24 |

    {slide.title}

    25 | 26 | 27 |
    28 |

    29 | Create sticky rows or columns (eg spreadsheet) with MultiGrid. 30 |

    31 | 32 | 33 |
    34 | 35 | 36 | Scroll around; click to edit a cell above. 37 |
    38 |
    39 |
    40 |
    41 | 42 | 43 | 47 | 48 | 49 | 50 | 54 | 55 |
    56 | ); 57 | } 58 | 59 | slide.contextTypes = { 60 | list: PropTypes.array.isRequired 61 | }; 62 | 63 | slide.title = 'Sticky rows & columns'; 64 | 65 | export default slide; 66 | -------------------------------------------------------------------------------- /src/Slides/504.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import DemoCollection from '../Components/DemoCollection'; 5 | 6 | const source = require('raw!../../examples/collection.js'); 7 | 8 | const DIM_LINES = [ 9 | [[0,21]], 10 | [[10,30]], 11 | [[0,10], [21,30]] 12 | ] 13 | 14 | const slide = ({ stepIndex }, { list }) => { 15 | return ( 16 | 17 |

    {slide.title}

    18 | 19 | 20 |
    21 |

    22 | For more complex layouts (eg Pinterest layout, Gantt chart) use Collection. 23 |

    24 | 25 | 26 | 27 | 28 |
    29 |
    30 | 31 | 32 | 36 | 37 |
    38 | ); 39 | } 40 | 41 | slide.contextTypes = { 42 | list: PropTypes.array.isRequired 43 | }; 44 | 45 | slide.title = 'Collection demo'; 46 | 47 | export default slide; 48 | -------------------------------------------------------------------------------- /src/Slides/600.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | 4 | const slide = () => ( 5 | 6 |

    {slide.title}

    7 |
    8 | ); 9 | 10 | slide.title = 'Bonus slide: Common integration mistakes'; 11 | 12 | export default slide; 13 | -------------------------------------------------------------------------------- /src/Slides/601.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | import warningImage from '../../public/v9-dev-mode-style-warning.png'; 5 | 6 | const source = require('raw!../../examples/forgotten-styles.js') 7 | 8 | const slide = () => ( 9 | 10 |

    {slide.title}

    11 | 12 |

    Most common mistake: forgot to set the style.

    13 |
    14 | 15 | 19 | 20 | 21 |
    22 |

    Version 9 now logs dev warning for this!

    23 |

    24 | 28 |

    29 |
    30 |
    31 |
    32 | ); 33 | 34 | slide.title = 'Styles'; 35 | 36 | export default slide; 37 | -------------------------------------------------------------------------------- /src/Slides/602.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Code, Step } from 'react-presents'; 3 | import ContentSlide from '../Presentation/ContentSlide'; 4 | 5 | const sourceA = require('raw!../../examples/pass-through-props.js'); 6 | const sourceB = require('raw!../../examples/force-update.js'); 7 | 8 | const slide = ({ stepIndex }) => ( 9 | 10 |

    {slide.title}

    11 | 12 | 13 |
    14 |
      15 |
    • All components extend PureComponent
    • 16 |
    • No access to array/collection
    • 17 |
    18 | 19 |
    20 |

    What about...

    21 |
      22 |
    • Sorting?
    • 23 |
    • State?
    • 24 |
    25 |
    26 |
    27 |
    28 |
    29 | 30 | 31 |

    32 | Let react-virtualized know that something external has changed! 33 |

    34 |
    35 | 36 | 37 |
    38 | 39 |
    40 |

    The simplest way is with pass-through properties:

    41 | 45 |
    46 |
    47 | 48 | 49 |
    50 |

    But you can also use Api methods (eg forceUpdate)

    51 | 55 |
    56 |
    57 |
    58 |
    59 |
    60 | ); 61 | 62 | slide.title = 'Pure components'; 63 | 64 | export default slide; 65 | -------------------------------------------------------------------------------- /src/Slides/700.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleSlide from '../Presentation/TitleSlide'; 3 | import styled from 'styled-components'; 4 | 5 | const Container = styled.div` 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: center; 10 | `; 11 | 12 | const Icon = styled.i` 13 | font-size: 2rem !important; 14 | line-height: 2rem !important; 15 | margin: 0 0.5rem; 16 | color: #ccc; 17 | `; 18 | 19 | const Spacer = styled.div` 20 | width: 3rem; 21 | `; 22 | 23 | const slide = () => ( 24 | 25 |

    {slide.title}

    26 |

    27 | slides @ github.com/bvaughn/forward-js-2017 28 |

    29 | 30 | @bvaughn 31 | 32 | @brian_d_vaughn 33 | 34 |
    35 | ); 36 | 37 | slide.title = 'Questions?' 38 | 39 | export default slide; 40 | -------------------------------------------------------------------------------- /src/Utils/generateRandomList.js: -------------------------------------------------------------------------------- 1 | const BADGE_COLORS = ['#f44336', '#3f51b5', '#4caf50', '#ff9800', '#2196f3', '#374046', '#cddc39', '#2196f3', '#9c27b0', '#ffc107', '#009688', '#673ab7', '#ffeb3b', '#cddc39', '#795548'] 2 | const NAMES = ['Peter Brimer', 'Tera Gaona', 'Kandy Liston', 'Lonna Wrede', 'Kristie Yard', 'Raul Host', 'Yukiko Binger', 'Velvet Natera', 'Donette Ponton', 'Loraine Grim', 'Shyla Mable', 'Marhta Sing', 'Alene Munden', 'Holley Pagel', 'Randell Tolman', 'Wilfred Juneau', 'Naida Madson', 'Marine Amison', 'Glinda Palazzo', 'Lupe Island', 'Cordelia Trotta', 'Samara Berrier', 'Era Stepp', 'Malka Spradlin', 'Edward Haner', 'Clemencia Feather', 'Loretta Rasnake', 'Dana Hasbrouck', 'Sanda Nery', 'Soo Reiling', 'Apolonia Volk', 'Liliana Cacho', 'Angel Couchman', 'Yvonne Adam', 'Jonas Curci', 'Tran Cesar', 'Buddy Panos', 'Rosita Ells', 'Rosalind Tavares', 'Renae Keehn', 'Deandrea Bester', 'Kelvin Lemmon', 'Guadalupe Mccullar', 'Zelma Mayers', 'Laurel Stcyr', 'Edyth Everette', 'Marylin Shevlin', 'Hsiu Blackwelder', 'Mark Ferguson', 'Winford Noggle', 'Shizuko Gilchrist', 'Roslyn Cress', 'Nilsa Lesniak', 'Agustin Grant', 'Earlie Jester', 'Libby Daigle', 'Shanna Maloy', 'Brendan Wilken', 'Windy Knittel', 'Alice Curren', 'Eden Lumsden', 'Klara Morfin', 'Sherryl Noack', 'Gala Munsey', 'Stephani Frew', 'Twana Anthony', 'Mauro Matlock', 'Claudie Meisner', 'Adrienne Petrarca', 'Pearlene Shurtleff', 'Rachelle Piro', 'Louis Cocco', 'Susann Mcsweeney', 'Mandi Kempker', 'Ola Moller', 'Leif Mcgahan', 'Tisha Wurster', 'Hector Pinkett', 'Benita Jemison', 'Kaley Findley', 'Jim Torkelson', 'Freda Okafor', 'Rafaela Markert', 'Stasia Carwile', 'Evia Kahler', 'Rocky Almon', 'Sonja Beals', 'Dee Fomby', 'Damon Eatman', 'Alma Grieve', 'Linsey Bollig', 'Stefan Cloninger', 'Giovanna Blind', 'Myrtis Remy', 'Marguerita Dostal', 'Junior Baranowski', 'Allene Seto', 'Margery Caves', 'Nelly Moudy', 'Felix Sailer'] 3 | const ROW_HEIGHTS = [50, 75, 100] 4 | 5 | const loremIpsum = [ 6 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 7 | 'Phasellus vulputate odio commodo tortor sodales, et vehicula ipsum viverra.', 8 | 'Cras tincidunt nisi in urna molestie varius.', 9 | 'Curabitur ac enim dictum arcu varius fermentum vel sodales dui.', 10 | 'Ut tristique augue at congue molestie.', 11 | 'Cras eget enim nec odio feugiat tristique eu quis ante.', 12 | 'Phasellus eget enim vitae nunc luctus sodales a eu erat.', 13 | 'Nulla bibendum quam id velit blandit dictum.', 14 | 'Donec dignissim mi ac libero feugiat, vitae lacinia odio viverra.', 15 | 'Praesent vel lectus venenatis, elementum mauris vitae, ullamcorper nulla.', 16 | 'Quisque sollicitudin nulla nec tellus feugiat hendrerit.', 17 | 'Vestibulum a eros accumsan, lacinia eros non, pretium diam.', 18 | 'Donec ornare felis et dui hendrerit, eget bibendum nibh interdum.', 19 | 'Donec nec diam vel tellus egestas lobortis.', 20 | 'Sed ornare nisl sit amet dolor pellentesque, eu fermentum leo interdum.', 21 | 'Sed eget mauris condimentum, molestie justo eu, feugiat felis.', 22 | 'Sed luctus justo vitae nibh bibendum blandit.', 23 | 'Nulla ac eros vestibulum, mollis ante eu, rutrum nulla.', 24 | 'Sed cursus magna ut vehicula rutrum.' 25 | ] 26 | 27 | export default function generateRandomList (count = 15e3) { 28 | const list = [] 29 | 30 | for (var i = 0; i < count; i++) { 31 | list.push({ 32 | age: 20 + Math.round(Math.random() * 50), 33 | color: BADGE_COLORS[i % BADGE_COLORS.length], 34 | index: i, 35 | left: Math.round(Math.random() * 20e3), 36 | name: NAMES[i % NAMES.length], 37 | random: loremIpsum[i % loremIpsum.length], 38 | size: ROW_HEIGHTS[Math.floor(Math.random() * ROW_HEIGHTS.length)], 39 | top: Math.round(Math.random() * 20e3) 40 | }) 41 | } 42 | 43 | return list 44 | } 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | // Import global stylesheets here 6 | import '../lib/font-awesome/css/font-awesome.min.css'; 7 | 8 | ReactDOM.render( 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /src/shared-list-styles.css: -------------------------------------------------------------------------------- 1 | .List { 2 | width: 15rem; 3 | height: 15rem; 4 | overflow: auto; 5 | font-size: 12px; 6 | border: 1px solid #CFD8DC; 7 | margin: 1rem 0; 8 | } 9 | 10 | .ListWithBorderRadius { 11 | border-radius: 1rem; 12 | border-width: 4px; 13 | overflow: auto; 14 | } 15 | 16 | .ListRow { 17 | padding: 0 0.5rem; 18 | width: 100%; 19 | height: 40px; 20 | line-height: 40px; 21 | transition: 0 all; 22 | white-space: pre; 23 | overflow: hidden; 24 | text-overflow: ellipsis; 25 | display: flex; 26 | flex-direction: row; 27 | align-items: center; 28 | border-bottom: 1px solid #e9e9e9; 29 | } 30 | 31 | .ListRowActived { 32 | background-color: #e9e9e9; 33 | } 34 | 35 | .RowNumber { 36 | flex: 0 0 2rem; 37 | display: inline-block; 38 | width: 2rem; 39 | height: 2rem; 40 | line-height: 2rem; 41 | border-radius: 2rem; 42 | text-align: center; 43 | color: #fff; 44 | } 45 | 46 | .RowStack { 47 | flex: 1 0 5.25rem; 48 | margin: 0 0.5rem; 49 | font-size: 20px; 50 | } 51 | 52 | .RowName { 53 | font-size: .8rem; 54 | line-height: .8rem; 55 | font-weight: bold; 56 | } 57 | 58 | .RowRowNumber { 59 | font-size: 0.6rem; 60 | line-height: 0.6rem; 61 | margin-top: 0.25rem; 62 | } 63 | 64 | .RowStar { 65 | flex: 0 0 auto; 66 | font-size: 1rem !important; 67 | color: #FF502F !important; 68 | } 69 | 70 | .ListScrolling { 71 | font-style: italic; 72 | opacity: 0.5; 73 | } 74 | --------------------------------------------------------------------------------