A Pen by Matthew Greenberg on CodePen.
Last active
July 12, 2018 15:00
-
-
Save e1blue/52c41aab98838dff43a8de0dfa7549fa to your computer and use it in GitHub Desktop.
User List (react + gsap)
This file contains hidden or 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
| <div id="app"></div> |
This file contains hidden or 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
| const {SortableContainer, SortableElement, arrayMove} = window.SortableHOC; | |
| const SortableList = SortableContainer(({items}) => { | |
| return ( | |
| <ul className="list"> | |
| {items.map((value, index) => ( | |
| <SortableItem key={`item-${index}`} index={index} value={value} /> | |
| ))} | |
| </ul> | |
| ); | |
| }); | |
| const SortableItem = SortableElement(({value}) => { | |
| return ( | |
| <li className="todo"> | |
| <i className="fa fa-user-circle" /> | |
| <div className="info-holder"> | |
| <h3 className="name">{value.name}</h3> | |
| <h5 className="role">{value.role}</h5> | |
| <h6 className="exp">{`${value.xp} exp`}</h6> | |
| </div> | |
| </li> | |
| ); | |
| }); | |
| class AddUser extends React.Component { | |
| constructor(props) { | |
| super(props); | |
| this.inputRef = React.createRef() | |
| } | |
| state = {name: '', value: 1, activeSkill: ''} | |
| componentDidMount() { | |
| const tlAdduser = new TimelineMax() | |
| tlAdduser.staggerTo('.add-user-block', 0.5, { | |
| opacity: 1, | |
| scale: 1, | |
| ease: Bounce.easeOut, | |
| }, 0.2, 0.3) | |
| tlAdduser.to('.add-user-button', 0.6, { | |
| opacity: 1, | |
| }) | |
| this.inputRef.current.focus() | |
| } | |
| handleChange = e => { | |
| this.setState({value: event.target.value}); | |
| } | |
| renderSkills() { | |
| const skills = ['Front End', 'Back End', 'UX', 'UI', 'Dev Ops', 'Full Stack'] | |
| return skills.map((skill, i) => { | |
| let modiferClass = '' | |
| if (this.state.activeSkill === skills[i]) { | |
| modiferClass='active' | |
| } | |
| return ( | |
| <div className={`${modiferClass} skill`} onClick={() => { | |
| const skill = skills[i] | |
| this.setState(state => { | |
| return {activeSkill: skill} | |
| }) | |
| }}> | |
| {skill} | |
| </div> | |
| ) | |
| }) | |
| } | |
| render() { | |
| const {name, value, activeSkill} = this.state | |
| const yearString = value.toString() === '1' ? 'year' : 'years' | |
| return ( | |
| <div className="add-user"> | |
| <div className="add-user-block"> | |
| <h6 className='name'>Name</h6> | |
| <input ref={this.inputRef} className='name-input' value={this.state.name} onChange={e => { | |
| const value = e.target.value | |
| this.setState(state => { | |
| return {name: value} | |
| }) | |
| }} /> | |
| </div> | |
| <div className="add-user-block add-user-block--exp"> | |
| <h6 className='expirience-title'>Expirience</h6> | |
| <div className="slider-wrapper"> | |
| <span className='exp-value'>{value + ' ' + yearString}</span> | |
| <input | |
| type="range" | |
| min="0" | |
| max="10" | |
| value={this.state.value} | |
| onChange={this.handleChange} | |
| className="slider" | |
| id="myRange" | |
| /> | |
| <div className="range-wrapper"> | |
| <span>0</span> | |
| <span>10+</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="add-user-block"> | |
| <h6 className='name'>Skill</h6> | |
| <div className="skill-wrapper"> | |
| {this.renderSkills()} | |
| </div> | |
| </div> | |
| <div className="button-wrapper"> | |
| <button disabled={name === '' || activeSkill === ''}onClick={() => { | |
| this.props.addUser({name, value, activeSkill}) | |
| }} className="add-user-button"><span className='fa fa-check' /></button> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| } | |
| class SortableComponent extends React.Component { | |
| constructor(props) { | |
| super(props); | |
| this.sortableListRef = React.createRef(); | |
| } | |
| state = { | |
| items: [{name: 'Trey Anastasio', role: 'Front End Developer', xp: '3 years'}, {name: 'Suzy Greenberg', role: 'Front End Developer', xp: '1 years'}, {name: 'Mike Gordon', role: 'Front End Developer', xp: '6 years'}, {name: 'John Fishman', role: 'UX Designer', xp: '2 years'}, {name: 'Page McConnell', role: 'Front End Developer', xp: '5 years'}, {name: 'Mickey Hart', role: 'Front End Developer', xp: '1 years'}], | |
| }; | |
| componentDidUpdate(prevProps) { | |
| if (prevProps.hideItems === false && this.props.hideItems === true ){ | |
| this.launchHideAnimation() | |
| } | |
| if (prevProps.hideItems === true && this.props.hideItems === false ){ | |
| this.launchShowAnimation() | |
| } | |
| } | |
| launchHideAnimation() { | |
| const tl = new TimelineMax(); | |
| const todos = this.sortableListRef.current.container.children | |
| const reversedArray = Array.from(todos).reverse() | |
| tl.staggerTo(reversedArray, 0.3, { | |
| opacity: 0, | |
| ease: Power1.easeOut, | |
| }, 0.05) | |
| tl.to(reversedArray, 0, { | |
| scale: 0, | |
| }) | |
| } | |
| launchShowAnimation() { | |
| const todos = this.sortableListRef.current.container.children | |
| TweenMax.staggerTo(todos, 0.75, { | |
| scale: 1, | |
| opacity: 1, | |
| ease: Bounce.easeOut | |
| }, 0.05); | |
| } | |
| addUser = (user) => { | |
| let userList = this.state.items | |
| const newUser = {name: user.name, role: user.activeSkill, xp: `${user.value} years`} | |
| userList.unshift(newUser) | |
| this.setState(state => { | |
| return { items: userList} | |
| }, this.props.toggleItems ) | |
| } | |
| onSortEnd = ({oldIndex, newIndex}) => { | |
| this.setState({ | |
| items: arrayMove(this.state.items, oldIndex, newIndex), | |
| }); | |
| }; | |
| renderContent() { | |
| if (this.props.hideItems) { | |
| return <AddUser addUser={this.addUser} /> | |
| } | |
| } | |
| render() { | |
| return ( | |
| <React.Fragment> | |
| {this.renderContent()} | |
| <div className="sortable-list-wrapper"> | |
| <SortableList ref={this.sortableListRef} className="list" lockAxis='y' transitionDuration={500} items={this.state.items} onSortEnd={this.onSortEnd} /> | |
| </div> | |
| </React.Fragment> | |
| ) | |
| } | |
| } | |
| class AddButton extends React.Component { | |
| render() { | |
| const addButtonModifier = this.props.hideItems ? 'close' : '' | |
| return ( | |
| <div className="add-button-container"> | |
| <i onClick={this.props.toggleItems} className={`add-button fas fa-plus ${addButtonModifier}`} /> | |
| </div> | |
| ) | |
| } | |
| } | |
| class Phone extends React.Component { | |
| state = { | |
| hideItems: false, | |
| } | |
| toggleItems = () => { | |
| this.setState(state => { | |
| return {hideItems: !state.hideItems} | |
| }) | |
| } | |
| render() { | |
| return ( | |
| <div className="phone"> | |
| <SortableComponent toggleItems={this.toggleItems} hideItems={this.state.hideItems} /> | |
| <AddButton hideItems={this.state.hideItems} toggleItems={this.toggleItems} /> | |
| </div> | |
| ) | |
| } | |
| } | |
| ReactDOM.render(<Phone/>, document.getElementById('app')); |
This file contains hidden or 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
| <script src="https://npmcdn.com/react-sortable-hoc/dist/umd/react-sortable-hoc.min.js"></script> | |
| <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> | |
| <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.2/prop-types.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.1/TweenMax.min.js"></script> |
This file contains hidden or 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
| $white: #FCFCFC; | |
| $gray: #f1f3f5; | |
| $black: #070708; | |
| $dark-gray: #5B5959; | |
| $orange: #DC852A; | |
| $yellow: #FCE09C; | |
| $green-gradient: linear-gradient(15deg,#14af83,#15b89a); | |
| html, body { | |
| height: 100%; | |
| background: linear-gradient(45deg, darken($gray, 5), lighten(lightgray, 10)); | |
| } | |
| h1,h2,h3,h4,h5,h6,h7 { | |
| margin: 0; | |
| } | |
| body { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-family: Helvetica Neue; | |
| font-family: Roboto, sans-serif; | |
| } | |
| .sortable-list-wrapper { | |
| width: 95%; | |
| height: 94%; | |
| background: lighten(lightgray, 11); | |
| box-shadow: inset 1px 1px 20px rgba(0,0,0,0.1); | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| overflow: auto; | |
| } | |
| .list { | |
| padding: 0; | |
| width: 95%; | |
| max-height: 100%; | |
| margin: 0 auto; | |
| background: lighten(lightgray, 11); | |
| overflow: visible; | |
| overflow-y: scroll; | |
| cursor: ns-resize; | |
| } | |
| .todo { | |
| list-style: none; | |
| background: linear-gradient(15deg, #745fb5, lighten(#9a6dbb, 10)); | |
| width: 100%; | |
| margin: 1em auto; | |
| color: $white; | |
| -moz-user-select: -moz-none; | |
| -khtml-user-select: none; | |
| -webkit-user-select: none; | |
| /* | |
| Introduced in IE 10. | |
| See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ | |
| */ | |
| -ms-user-select: none; | |
| user-select: none; | |
| height: 5em; | |
| box-shadow: 1px 2px 6px rgba(0,0,0,0.4); | |
| display: flex; | |
| justify-content: flex-start; | |
| align-items: center; | |
| i { | |
| font-size: 30px; | |
| margin-left: 0.5em; | |
| } | |
| .info-holder { | |
| display: flex; | |
| flex-direction: column; | |
| margin-left: 1em; | |
| } | |
| .name { | |
| color: #333333ba; | |
| font-weight: 400; | |
| } | |
| .role { | |
| font-weight: 400; | |
| font-style: italic; | |
| margin-top: 5px; | |
| } | |
| .exp { | |
| font-weight: 400; | |
| color: lightgray; | |
| } | |
| } | |
| .phone { | |
| background: $white; | |
| opacity: 1; | |
| display: flex; | |
| justify-content: center; | |
| height: 550px; | |
| position: relative; | |
| width: 350px; | |
| box-shadow: 11px 10px 63px 14px rgba(0,0,0,0.18); | |
| } | |
| .add-button-container { | |
| background: white; | |
| position: absolute; | |
| height: 4em; | |
| width: 4em; | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| bottom: 30px; | |
| transform: scale(0.8); | |
| right:20px; | |
| box-shadow: 4px 3px 20px rgba(0,0,0,0.2); | |
| z-index: 1000000; | |
| } | |
| .add-button { | |
| position: absolute; | |
| font-size: 50px; | |
| color: #333; | |
| text-shadow: 3px 5px 20px rgba(0,0,0,0.3); | |
| transition: all 0.3s; | |
| &.close { | |
| transform: rotate(45deg); | |
| text-shadow: 0 0 0; | |
| } | |
| } | |
| .add-user { | |
| width: 95%; | |
| max-height: 95%; | |
| background: transparent; | |
| position: absolute; | |
| transform: translate(0%, -50%); | |
| top: 44.5%; | |
| z-index: 100; | |
| .add-user-block { | |
| height: 8em; | |
| margin: 0.5em auto; | |
| width: 95%; | |
| background: white; | |
| opacity: 0; | |
| box-shadow: 2px 1px 10px rgba(0,0,0,0.1); | |
| transform: scale(0); | |
| position: relative; | |
| color: #333; | |
| border-radius: 6px; | |
| position: relative; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| .skill-wrapper { | |
| margin: 0 auto; | |
| width: 90%; | |
| margin-top: 2em; | |
| } | |
| .skill { | |
| display: inline-block; | |
| width: 20%; | |
| margin: 0.5em; | |
| opacity: 0.6; | |
| font-size: 13px; | |
| background: #d3d3d3; | |
| color: #333; | |
| text-align: center; | |
| padding: 0.25em 0.75em; | |
| border-radius: 10px; | |
| transition: all 0.2s; | |
| cursor: pointer; | |
| &.active { | |
| background: lightgreen; | |
| color: green; | |
| } | |
| } | |
| .name { | |
| top: 20px; | |
| left: 20px; | |
| font-size: 14px; | |
| color: #333; | |
| position: absolute; | |
| } | |
| .name-input { | |
| border: none; | |
| border-bottom: 3px solid #333; | |
| margin: 1em auto; | |
| width: 80%; | |
| display: block; | |
| &:active, &:focus { | |
| outline: none; | |
| } | |
| } | |
| } | |
| } | |
| .add-user-block--exp { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| .expirience-title { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| font-size: 14px; | |
| } | |
| .exp-value { | |
| margin-bottom: 0.33em; | |
| color: darken(lightgray, 30); | |
| font-weight: 600; | |
| } | |
| } | |
| .slider { | |
| -webkit-appearance: none; | |
| width: 90%; | |
| height: 10px; | |
| background: #d3d3d3; | |
| outline: none; | |
| border-radius: 10px; | |
| opacity: 0.7; | |
| -webkit-transition: .2s; | |
| transition: opacity .2s; | |
| } | |
| .slider:hover { | |
| opacity: 1; | |
| } | |
| .slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 23px; | |
| height: 23px; | |
| border-radius: 50%; | |
| background: #4CAF50; | |
| cursor: pointer; | |
| } | |
| .slider::-moz-range-thumb { | |
| width: 25px; | |
| height: 25px; | |
| background: #4CAF50; | |
| cursor: pointer; | |
| &::before { | |
| content: ''; | |
| height: 10px; | |
| width: 10px; | |
| background: red; | |
| position: absolute; | |
| top: 100%; | |
| color: red; | |
| } | |
| } | |
| .range-wrapper { | |
| display: flex; | |
| justify-content: space-between; | |
| width: 86%; | |
| margin-top: 8px; | |
| font-size: 12px; | |
| color: gray; | |
| } | |
| .slider-wrapper { | |
| display: flex; | |
| flex-direction: column; | |
| width: 90%; | |
| align-items: center; | |
| margin-top: 2em; | |
| } | |
| .button-wrapper { | |
| text-align: center; | |
| width: 100%; | |
| .add-user-button { | |
| border: none; | |
| color: white; | |
| opacity: 0; | |
| background: dodgerblue; | |
| padding: 0.25em 1em; | |
| border-radius: 10px; | |
| box-shadow: 1px 2px 10px rgba(0,0,0,0.3); | |
| cursor: pointer; | |
| &:disabled { | |
| background: gray; | |
| cursor: not-allowed; | |
| } | |
| &:active, &:focus{ | |
| outline: none; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment