This repository has been archived by the owner on Nov 3, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from twitter-fabric/feature/crossfade-demo
Crossfade example
- Loading branch information
Showing
6 changed files
with
302 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Component for a VelocityTransitionGroup that crossfades between its children. | ||
|
||
// To use this component, render with a single child that contains the "loading" version of your | ||
// UI. When data has loaded, switch the "key" of this child so that React considers it a brand | ||
// new element and triggers the enter / leave effects. The two versions of the UI are expected to | ||
// have identical heights. | ||
|
||
// Properties on this component are applied to the VelocityTransitionGroup component that this | ||
// delegates to. A postion: 'relative' style is also applied since the loading effect requires | ||
// position: 'absolute' on the child. | ||
|
||
// Use the property "opaque" if the children have opaque backgrounds. This will make the new element | ||
// come in 100% opacity and fade the old element out from on top of it. (Without this, opaque | ||
// elements end up bleeding the background behind the LoadingCrossfadeComponent through.) | ||
|
||
var React = require('react'); | ||
var _ = require('lodash'); | ||
var VelocityTransitionGroup = require('../../lib/velocity-transition-group'); | ||
|
||
var LoadingCrossfadeComponent = React.createClass({ | ||
displayName: 'LoadingCrossfadeComponent', | ||
|
||
propTypes: { | ||
opaque: React.PropTypes.bool, | ||
duration: React.PropTypes.number, | ||
}, | ||
|
||
getDefaultProps: function () { | ||
return { | ||
duration: 350, | ||
}; | ||
}, | ||
|
||
render: function () { | ||
// position: 'relative' lets us absolutely-position the leaving child during the fade. | ||
var style = _.defaults((this.props.style || {}), { position: 'relative' }); | ||
|
||
var transitionGroupProps = _.defaults(_.omit(this.props, 'children', 'style'), { | ||
component: 'div', | ||
style: style, | ||
|
||
enter: { | ||
animation: { opacity: 1 }, | ||
duration: this.props.duration, | ||
style: { | ||
// If we're animating opaque backgrounds then we just render the new element under the | ||
// old one and fade out the old one. Without this, at e.g. the crossfade midpoint of | ||
// 50% opacity for old and 50% opacity for new, the parent background ends up bleeding | ||
// through 25%, which makes things look not smooth at all. | ||
opacity: this.props.opaque ? 1 : 0, | ||
|
||
// We need to clear out all the styles that "leave" puts on the element. | ||
position: 'relative', | ||
top: '', | ||
left: '', | ||
bottom: '', | ||
right: '', | ||
zIndex: '', | ||
}, | ||
}, | ||
|
||
leave: { | ||
animation: { opacity: 0 }, | ||
duration: this.props.duration, | ||
style: { | ||
// 'absolute' so the 2 elements overlap for a crossfade | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
bottom: 0, | ||
right: 0, | ||
zIndex: 1, | ||
}, | ||
}, | ||
}); | ||
|
||
return React.createElement(VelocityTransitionGroup, transitionGroupProps, this.props.children); | ||
}, | ||
}); | ||
|
||
module.exports = LoadingCrossfadeComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
.loading-placeholder-light, | ||
.loading-placeholder-dark { | ||
position: relative; | ||
} | ||
|
||
/* Gray, translucent bar to show "loading." Renders as 86% of the parent's height, vertically | ||
centered, and 100% of its width. */ | ||
.loading-placeholder-light:before, | ||
.loading-placeholder-dark:before { | ||
content: ' '; | ||
display: block; | ||
position: absolute; | ||
top: 7%; | ||
height: 86%; | ||
width: 100%; | ||
} | ||
|
||
.loading-placeholder-full:before { | ||
top: 0; | ||
height: 100%; | ||
} | ||
|
||
/* Inserts a non-breaking space into the parent to guarantee that it has at least one line's | ||
worth of height. (Necessary for the 'before' block to do a relatively-sized height.) */ | ||
.loading-placeholder-light:after, | ||
.loading-placeholder-dark:after { | ||
content: '\00a0'; | ||
} | ||
|
||
.loading-placeholder-light:before { | ||
background-color: rgba(235, 237, 241, 0.1); | ||
} | ||
|
||
.loading-placeholder-dark:before { | ||
background-color: rgba(235, 237, 241, 0.5); | ||
} | ||
|
||
span.loading-placeholder-light, | ||
span.loading-placeholder-dark { | ||
/* Useful for being able to set an explicit width on the placeholder. */ | ||
display: inline-block; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
var _ = require('lodash'); | ||
|
||
var React = require('react'); | ||
var VelocityComponent = require('../../lib/velocity-component'); | ||
var VelocityTransitionGroup = require('../../lib/velocity-transition-group'); | ||
var VelocityHelpers = require('../../lib/velocity-helpers'); | ||
|
||
var Box = require('../components/box'); | ||
var EmojiSpan = require('../components/emoji-span'); | ||
var LoadingCrossfadeComponent = require('../components/loading-crossfade-component'); | ||
|
||
require('../css/loading-placeholder.css'); | ||
|
||
var LOCATION_COUNT = 4; | ||
|
||
var BUILDINGS = ['🏠', '🏡', '🏢', '🏬', '🏭', '🏣', '🏤', '🏥', '🏦', '🏨', '🏩', '💒', '⛪', '🏪', '🏫']; | ||
var CAPTIALS = ['Montgomery', 'Juneau', 'Phoenix', 'Little Rock', 'Sacramento', 'Denver', 'Hartford', 'Dover', | ||
'Tallahassee', 'Atlanta', 'Honolulu', 'Boise', 'Springfield', 'Indianapolis', 'Des Moines', 'Topeka', 'Frankfort', | ||
'Baton Rouge', 'Augusta', 'Annapolis', 'Boston', 'Lansing', 'St. Paul', 'Jackson', 'Jefferson City', 'Helena', | ||
'Lincoln', 'Carson City', 'Concord', 'Trenton', 'Santa Fe', 'Albany', 'Raleigh', 'Bismarck', 'Columbus', | ||
'Oklahoma City', 'Salem', 'Harrisburg', 'Providence', 'Columbia', 'Pierre', 'Nashville', 'Austin', 'Salt Lake City', | ||
'Montpelier', 'Richmond', 'Olympia', 'Charleston', 'Madison', 'Cheyenne']; | ||
|
||
var CrossfadeExample = React.createClass({ | ||
displayName: 'CrossfadeExample', | ||
|
||
getInitialState: function () { | ||
return { | ||
expanded: false, | ||
items: null, | ||
duration: 500, | ||
}; | ||
}, | ||
|
||
componentWillUnmount: function () { | ||
window.clearTimeout(this.locationTimeout); | ||
}, | ||
|
||
whenToggleClicked: function () { | ||
if (this.state.expanded) { | ||
this.setState({ | ||
expanded: false, | ||
items: null, | ||
}); | ||
|
||
window.clearTimeout(this.locationTimeout); | ||
} else { | ||
this.setState({ | ||
expanded: true, | ||
items: null, | ||
}); | ||
|
||
this.locationTimeout = window.setTimeout(this.loadLocations, this.state.duration * 1.5); | ||
} | ||
}, | ||
|
||
loadLocations: function () { | ||
this.setState({ | ||
items: Array.apply(null, Array(LOCATION_COUNT)).map(function () { | ||
return { | ||
building: _.sample(BUILDINGS), | ||
city: _.sample(CAPTIALS), | ||
}; | ||
}), | ||
}); | ||
}, | ||
|
||
whenOptionClicked: function (event) { | ||
this.setState({ duration: parseInt(event.target.value) }); | ||
}, | ||
|
||
render: function () { | ||
var groupStyle = { | ||
width: 198, | ||
}; | ||
|
||
var boxStyle = { | ||
margin: '-10px 0 0 0', | ||
}; | ||
|
||
var toggleStyle = { | ||
width: 200, | ||
backgroundColor: '#3f83b7', | ||
color: 'white', | ||
padding: 8, | ||
fontSize: 13, | ||
lineHeight: '18px', | ||
cursor: 'pointer', | ||
}; | ||
|
||
var arrowStyle = { | ||
display: 'block', | ||
fontSize: 18, | ||
}; | ||
|
||
return ( | ||
<div className="flex-box flex-1 flex-column align-items-center" style={boxStyle}> | ||
<div className="flex-box justify-content-space-between" style={toggleStyle} onClick={this.whenToggleClicked}> | ||
Points of Interest | ||
<VelocityComponent animation={{rotateZ: this.state.expanded ? 0 : -180}} duration={this.state.duration}> | ||
<EmojiSpan style={arrowStyle}>👇</EmojiSpan> | ||
</VelocityComponent> | ||
</div> | ||
|
||
<VelocityTransitionGroup component="div" className="flex-1" style={groupStyle} | ||
enter={{animation: 'slideDown', duration: this.state.duration}} | ||
leave={{animation: 'slideUp', duration: this.state.duration}}> | ||
{this.state.expanded ? this.renderLocations() : null} | ||
</VelocityTransitionGroup> | ||
|
||
<form style={{fontSize: 12}}> | ||
<label> | ||
<input type="radio" name="speed" value={500} checked={this.state.duration === 500} onChange={this.whenOptionClicked}/> Fast | ||
</label> | ||
| ||
<label> | ||
<input type="radio" name="speed" value={2000} checked={this.state.duration === 2000} onChange={this.whenOptionClicked}/> Slow | ||
</label> | ||
</form> | ||
</div> | ||
); | ||
}, | ||
|
||
renderLocations: function () { | ||
var boxStyle = { | ||
backgroundColor: '#fefefe', | ||
padding: '5px 10px', | ||
}; | ||
|
||
var locations = this.state.items != null ? this.state.items : Array.apply(null, Array(LOCATION_COUNT)); | ||
|
||
return ( | ||
<LoadingCrossfadeComponent duration={this.state.duration * .75}> | ||
<div key={this.state.items != null ? 'locations' : 'loading'} style={boxStyle}>{locations.map(this.renderLocation)}</div> | ||
</LoadingCrossfadeComponent> | ||
); | ||
}, | ||
|
||
renderLocation: function(location, i) { | ||
location = location || {building: '', city: ''}; | ||
|
||
var emojiStyle = { | ||
fontSize: 30, | ||
display: 'block', | ||
width: 34.5, | ||
height: 31, | ||
}; | ||
|
||
var rowStyle = { | ||
padding: '5px 0', | ||
}; | ||
|
||
var cityStyle = { | ||
fontSize: 16, | ||
margin: '0 0 0 5px', | ||
}; | ||
|
||
return ( | ||
<div className="flex-box align-items-center" style={rowStyle}> | ||
<EmojiSpan key={i} | ||
className={location.city == '' ? 'loading-placeholder-dark loading-placeholder-full' : ''} | ||
style={emojiStyle}>{location.building}</EmojiSpan> | ||
<div style={cityStyle} className={'flex-1 ' + (location.city == '' ? 'loading-placeholder-dark loading-placeholder-full' : '')}> | ||
{location.city} | ||
</div> | ||
</div> | ||
); | ||
} | ||
}); | ||
|
||
module.exports = CrossfadeExample; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters