Responsively changing layout to single-column + nav on smaller screens
This commit is contained in:
		
							parent
							
								
									e2ff39bf5d
								
							
						
					
					
						commit
						45776b55b0
					
				
					 13 changed files with 220 additions and 100 deletions
				
			
		|  | @ -15,12 +15,15 @@ import { | |||
|   hashHistory, | ||||
|   IndexRoute | ||||
| }                         from 'react-router'; | ||||
| import UI                 from '../features/ui'; | ||||
| import Account            from '../features/account'; | ||||
| import Status             from '../features/status'; | ||||
| import GettingStarted     from '../features/getting_started'; | ||||
| import PublicTimeline     from '../features/public_timeline'; | ||||
| import UI                 from '../features/ui'; | ||||
| import AccountTimeline    from '../features/account_timeline'; | ||||
| import HomeTimeline       from '../features/home_timeline'; | ||||
| import MentionsTimeline   from '../features/mentions_timeline'; | ||||
| import Compose            from '../features/compose'; | ||||
| 
 | ||||
| const store = configureStore(); | ||||
| 
 | ||||
|  | @ -77,6 +80,9 @@ const Mastodon = React.createClass({ | |||
|         <Router history={hashHistory}> | ||||
|           <Route path='/' component={UI}> | ||||
|             <IndexRoute component={GettingStarted} /> | ||||
|             <Route path='/statuses/new' component={Compose} /> | ||||
|             <Route path='/statuses/home' component={HomeTimeline} /> | ||||
|             <Route path='/statuses/mentions' component={MentionsTimeline} /> | ||||
|             <Route path='/statuses/all' component={PublicTimeline} /> | ||||
|             <Route path='/statuses/:statusId' component={Status} /> | ||||
|             <Route path='/accounts/:accountId' component={Account}> | ||||
|  |  | |||
							
								
								
									
										28
									
								
								app/assets/javascripts/components/features/compose/index.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/assets/javascripts/components/features/compose/index.jsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| import Drawer               from '../ui/components/drawer'; | ||||
| import ComposeFormContainer from '../ui/containers/compose_form_container'; | ||||
| import FollowFormContainer  from '../ui/containers/follow_form_container'; | ||||
| import UploadFormContainer  from '../ui/containers/upload_form_container'; | ||||
| import NavigationContainer  from '../ui/containers/navigation_container'; | ||||
| import PureRenderMixin      from 'react-addons-pure-render-mixin'; | ||||
| 
 | ||||
| const Compose = React.createClass({ | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|       <Drawer> | ||||
|         <div style={{ flex: '1 1 auto' }}> | ||||
|           <NavigationContainer /> | ||||
|           <ComposeFormContainer /> | ||||
|           <UploadFormContainer /> | ||||
|         </div> | ||||
| 
 | ||||
|         <FollowFormContainer /> | ||||
|       </Drawer> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default Compose; | ||||
|  | @ -0,0 +1,19 @@ | |||
| import PureRenderMixin     from 'react-addons-pure-render-mixin'; | ||||
| import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column              from '../ui/components/column'; | ||||
| 
 | ||||
| const HomeTimeline = React.createClass({ | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|       <Column icon='home' heading='Home'> | ||||
|         <StatusListContainer type='home' /> | ||||
|       </Column> | ||||
|     ); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default HomeTimeline; | ||||
|  | @ -0,0 +1,19 @@ | |||
| import PureRenderMixin     from 'react-addons-pure-render-mixin'; | ||||
| import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column              from '../ui/components/column'; | ||||
| 
 | ||||
| const MentionsTimeline = React.createClass({ | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|       <Column icon='at' heading='Mentions'> | ||||
|         <StatusListContainer type='mentions' /> | ||||
|       </Column> | ||||
|     ); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default MentionsTimeline; | ||||
|  | @ -1,43 +1,14 @@ | |||
| import { connect }         from 'react-redux'; | ||||
| import PureRenderMixin     from 'react-addons-pure-render-mixin'; | ||||
| import ImmutablePropTypes  from 'react-immutable-proptypes'; | ||||
| import StatusList          from '../../components/status_list'; | ||||
| import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column              from '../ui/components/column'; | ||||
| import Immutable           from 'immutable'; | ||||
| import { makeGetTimeline } from '../../selectors'; | ||||
| import { | ||||
|   updateTimeline, | ||||
|   refreshTimeline, | ||||
|   expandTimeline | ||||
|   updateTimeline | ||||
| }                          from '../../actions/timelines'; | ||||
| import { deleteStatus }    from '../../actions/statuses'; | ||||
| import { replyCompose }    from '../../actions/compose'; | ||||
| import { | ||||
|   favourite, | ||||
|   reblog, | ||||
|   unreblog, | ||||
|   unfavourite | ||||
| }                          from '../../actions/interactions'; | ||||
| 
 | ||||
| const makeMapStateToProps = () => { | ||||
|   const getTimeline = makeGetTimeline(); | ||||
| 
 | ||||
|   const mapStateToProps = (state) => ({ | ||||
|     statuses: getTimeline(state, 'public'), | ||||
|     me: state.getIn(['timelines', 'me']) | ||||
|   }); | ||||
| 
 | ||||
|   return mapStateToProps; | ||||
| }; | ||||
| 
 | ||||
| const PublicTimeline = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     statuses: ImmutablePropTypes.list.isRequired, | ||||
|     me: React.PropTypes.number.isRequired, | ||||
|     dispatch: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|  | @ -62,44 +33,14 @@ const PublicTimeline = React.createClass({ | |||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   handleReply (status) { | ||||
|     this.props.dispatch(replyCompose(status)); | ||||
|   }, | ||||
| 
 | ||||
|   handleReblog (status) { | ||||
|     if (status.get('reblogged')) { | ||||
|       this.props.dispatch(unreblog(status)); | ||||
|     } else { | ||||
|       this.props.dispatch(reblog(status)); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   handleFavourite (status) { | ||||
|     if (status.get('favourited')) { | ||||
|       this.props.dispatch(unfavourite(status)); | ||||
|     } else { | ||||
|       this.props.dispatch(favourite(status)); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   handleDelete (status) { | ||||
|     this.props.dispatch(deleteStatus(status.get('id'))); | ||||
|   }, | ||||
| 
 | ||||
|   handleScrollToBottom () { | ||||
|     this.props.dispatch(expandTimeline('public')); | ||||
|   }, | ||||
| 
 | ||||
|   render () { | ||||
|     const { statuses, me } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Column icon='globe' heading='Public'> | ||||
|         <StatusList statuses={statuses} me={me} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} onDelete={this.handleDelete} /> | ||||
|         <StatusListContainer type='public' /> | ||||
|       </Column> | ||||
|     ); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(makeMapStateToProps)(PublicTimeline); | ||||
| export default connect()(PublicTimeline); | ||||
|  |  | |||
|  | @ -29,6 +29,15 @@ const scrollTop = (node) => { | |||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const style = { | ||||
|   height: '100%', | ||||
|   boxSizing: 'border-box', | ||||
|   flex: '0 0 auto', | ||||
|   background: '#282c37', | ||||
|   display: 'flex', | ||||
|   flexDirection: 'column' | ||||
| }; | ||||
| 
 | ||||
| const Column = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|  | @ -56,10 +65,8 @@ const Column = React.createClass({ | |||
|       header = <ColumnHeader icon={this.props.icon} type={this.props.heading} onClick={this.handleHeaderClick} />; | ||||
|     } | ||||
| 
 | ||||
|     const style = { width: '330px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', marginBottom: '0', display: 'flex', flexDirection: 'column' }; | ||||
| 
 | ||||
|     return ( | ||||
|       <div style={style} onWheel={this.handleWheel}> | ||||
|       <div className='column' style={style} onWheel={this.handleWheel}> | ||||
|         {header} | ||||
|         {this.props.children} | ||||
|       </div> | ||||
|  |  | |||
|  | @ -1,12 +1,20 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| 
 | ||||
| const style = { | ||||
|   display: 'flex', | ||||
|   flex: '1 1 auto', | ||||
|   flexDirection: 'row', | ||||
|   justifyContent: 'flex-start', | ||||
|   overflowX: 'auto' | ||||
| }; | ||||
| 
 | ||||
| const ColumnsArea = React.createClass({ | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|       <div style={{ display: 'flex', flexDirection: 'row', flex: '1', justifyContent: 'flex-start', marginRight: '10px', marginBottom: '10px', overflowX: 'auto' }}> | ||||
|       <div className='columns-area' style={style}> | ||||
|         {this.props.children} | ||||
|       </div> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,12 +1,22 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| 
 | ||||
| const style = { | ||||
|   height: '100%', | ||||
|   flex: '0 0 auto', | ||||
|   boxSizing: 'border-box', | ||||
|   background: '#454b5e', | ||||
|   padding: '0', | ||||
|   display: 'flex', | ||||
|   flexDirection: 'column' | ||||
| }; | ||||
| 
 | ||||
| const Drawer = React.createClass({ | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|       <div style={{ width: '280px', flex: '0 0 auto', boxSizing: 'border-box', background: '#454b5e', margin: '10px', marginRight: '0', padding: '0', display: 'flex', flexDirection: 'column' }}> | ||||
|       <div className='drawer' style={style}> | ||||
|         {this.props.children} | ||||
|       </div> | ||||
|     ); | ||||
|  |  | |||
|  | @ -0,0 +1,38 @@ | |||
| import { Link } from 'react-router'; | ||||
| 
 | ||||
| const outerStyle = { | ||||
|   background: '#373b4a', | ||||
|   margin: '10px', | ||||
|   flex: '0 0 auto', | ||||
|   marginBottom: '0', | ||||
|   display: 'flex' | ||||
| }; | ||||
| 
 | ||||
| const tabStyle = { | ||||
|   display: 'block', | ||||
|   flex: '1 1 auto', | ||||
|   padding: '10px', | ||||
|   color: '#fff', | ||||
|   textDecoration: 'none', | ||||
|   fontSize: '12px', | ||||
|   fontWeight: '500', | ||||
|   borderBottom: '2px solid #373b4a' | ||||
| }; | ||||
| 
 | ||||
| const tabActiveStyle = { | ||||
|   borderBottom: '2px solid #2b90d9', | ||||
|   color: '#2b90d9' | ||||
| }; | ||||
| 
 | ||||
| const TabsBar = () => { | ||||
|   return ( | ||||
|     <div style={outerStyle}> | ||||
|       <Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/new'><i className='fa fa-fw fa-pencil' /> Compose</Link> | ||||
|       <Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/home'><i className='fa fa-fw fa-home' /> Home</Link> | ||||
|       <Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/mentions'><i className='fa fa-fw fa-at' /> Mentions</Link> | ||||
|       <Link style={tabStyle} activeStyle={tabActiveStyle} to='/statuses/all'><i className='fa fa-fw fa-globe' /> Public</Link> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default TabsBar; | ||||
|  | @ -1,47 +1,38 @@ | |||
| import ColumnsArea            from './components/columns_area'; | ||||
| import Column                 from './components/column'; | ||||
| import Drawer                 from './components/drawer'; | ||||
| import ComposeFormContainer   from './containers/compose_form_container'; | ||||
| import FollowFormContainer    from './containers/follow_form_container'; | ||||
| import UploadFormContainer    from './containers/upload_form_container'; | ||||
| import StatusListContainer    from './containers/status_list_container'; | ||||
| import NotificationsContainer from './containers/notifications_container'; | ||||
| import NavigationContainer    from './containers/navigation_container'; | ||||
| import PureRenderMixin        from 'react-addons-pure-render-mixin'; | ||||
| import LoadingBarContainer    from './containers/loading_bar_container'; | ||||
| import HomeTimeline           from '../home_timeline'; | ||||
| import MentionsTimeline       from '../mentions_timeline'; | ||||
| import Compose                from '../compose'; | ||||
| import MediaQuery             from 'react-responsive'; | ||||
| import TabsBar                from './components/tabs_bar'; | ||||
| 
 | ||||
| const UI = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     router: React.PropTypes.object | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     const layoutBreakpoint = 1024; | ||||
| 
 | ||||
|     return ( | ||||
|       <div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}> | ||||
|         <Drawer> | ||||
|           <div style={{ flex: '1 1 auto' }}> | ||||
|             <NavigationContainer /> | ||||
|             <ComposeFormContainer /> | ||||
|             <UploadFormContainer /> | ||||
|           </div> | ||||
| 
 | ||||
|           <FollowFormContainer /> | ||||
|         </Drawer> | ||||
| 
 | ||||
|         <ColumnsArea> | ||||
|           <Column icon='home' heading='Home'> | ||||
|             <StatusListContainer type='home' /> | ||||
|           </Column> | ||||
| 
 | ||||
|           <Column icon='at' heading='Mentions'> | ||||
|             <StatusListContainer type='mentions' /> | ||||
|           </Column> | ||||
|       <div style={{ flex: '0 0 auto', display: 'flex', flexDirection: 'column', width: '100%', height: '100%', background: '#1a1c23' }}> | ||||
|         <MediaQuery maxWidth={layoutBreakpoint}> | ||||
|           <TabsBar /> | ||||
|         </MediaQuery> | ||||
| 
 | ||||
|         <MediaQuery maxWidth={layoutBreakpoint} component={ColumnsArea}> | ||||
|           {this.props.children} | ||||
|         </ColumnsArea> | ||||
|         </MediaQuery> | ||||
| 
 | ||||
|         <MediaQuery minWidth={layoutBreakpoint}> | ||||
|           <ColumnsArea> | ||||
|             <Compose /> | ||||
|             <HomeTimeline /> | ||||
|             <MentionsTimeline /> | ||||
|             {this.props.children} | ||||
|           </ColumnsArea> | ||||
|         </MediaQuery> | ||||
| 
 | ||||
|         <NotificationsContainer /> | ||||
|         <LoadingBarContainer style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} /> | ||||
|  |  | |||
|  | @ -227,3 +227,31 @@ | |||
|     margin-bottom: 20px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .columns-area { | ||||
|   margin: 10px; | ||||
|   margin-left: 0; | ||||
| } | ||||
| 
 | ||||
| .column { | ||||
|   width: 330px; | ||||
| } | ||||
| 
 | ||||
| .drawer { | ||||
|   width: 280px; | ||||
| } | ||||
| 
 | ||||
| .column, .drawer { | ||||
|   margin-left: 10px; | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 1024px) { | ||||
|   .column, .drawer { | ||||
|     width: 100%; | ||||
|     margin: 0; | ||||
|   } | ||||
| 
 | ||||
|   .columns-area { | ||||
|     margin: 10px; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -38,5 +38,8 @@ | |||
|     "redux-thunk": "^2.1.0", | ||||
|     "reselect": "^2.5.4", | ||||
|     "sinon": "^1.17.6" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "react-responsive": "^1.1.5" | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										22
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								yarn.lock
									
										
									
									
									
								
							|  | @ -1541,6 +1541,10 @@ css-loader@0.25.0: | |||
|     postcss-modules-values "^1.1.0" | ||||
|     source-list-map "^0.1.4" | ||||
| 
 | ||||
| css-mediaquery@^0.1.2: | ||||
|   version "0.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" | ||||
| 
 | ||||
| css-select@~1.2.0: | ||||
|   version "1.2.0" | ||||
|   resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" | ||||
|  | @ -2359,6 +2363,10 @@ https-browserify@0.0.0: | |||
|   version "0.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.0.tgz#b3ffdfe734b2a3d4a9efd58e8654c91fce86eafd" | ||||
| 
 | ||||
| hyphenate-style-name@^1.0.0: | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.1.tgz#bc49b9446e02b4570641afdd29c1ce7609d1b9cc" | ||||
| 
 | ||||
| iconv-lite@^0.4.13, iconv-lite@~0.4.13: | ||||
|   version "0.4.13" | ||||
|   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" | ||||
|  | @ -2928,6 +2936,12 @@ mantra-core@^1.6.1: | |||
|     react-komposer "^1.9.0" | ||||
|     react-simple-di "^1.2.0" | ||||
| 
 | ||||
| matchmedia@^0.1.2: | ||||
|   version "0.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/matchmedia/-/matchmedia-0.1.2.tgz#cfd47f2bf68fbc7f5ea1bd3a3cf1715ecba3c1bd" | ||||
|   dependencies: | ||||
|     css-mediaquery "^0.1.2" | ||||
| 
 | ||||
| math-expression-evaluator@^1.2.14: | ||||
|   version "1.2.14" | ||||
|   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz#39511771ed9602405fba9affff17eb4d2a3843ab" | ||||
|  | @ -3823,6 +3837,14 @@ react-redux@^5.0.0-beta.3: | |||
|     lodash-es "^4.2.0" | ||||
|     loose-envify "^1.1.0" | ||||
| 
 | ||||
| react-responsive: | ||||
|   version "1.1.5" | ||||
|   resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-1.1.5.tgz#a7019a28817dcb601ef31d10d72f798a0d710a17" | ||||
|   dependencies: | ||||
|     hyphenate-style-name "^1.0.0" | ||||
|     matchmedia "^0.1.2" | ||||
|     object-assign "^4.0.1" | ||||
| 
 | ||||
| react-router@^2.8.0: | ||||
|   version "2.8.1" | ||||
|   resolved "https://registry.yarnpkg.com/react-router/-/react-router-2.8.1.tgz#73e9491f6ceb316d0f779829081863e378ee4ed7" | ||||
|  |  | |||
		Reference in a new issue