Mobile Responsive Styles (#580)

* WIP: Responsive styles

* Flip breakpoints, ensure doc doesn't spread

* Add MenuIcon

* Refactor Sidebar to share mobile responsive styles

* Fix accidental find/replace

* Tweak padding to take into account icon spacing
This commit is contained in:
Tom Moor
2018-02-10 23:24:12 -08:00
committed by GitHub
parent 4cc7338534
commit 6caba86751
17 changed files with 213 additions and 96 deletions

View File

@ -1,5 +1,6 @@
// @flow
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import Flex from 'shared/components/Flex';
import { layout, color } from 'shared/styles/constants';
@ -25,14 +26,20 @@ const Actions = styled(Flex)`
position: fixed;
top: 0;
right: 0;
padding: ${layout.vpadding} ${layout.hpadding} 8px 8px;
left: 0;
border-radius: 3px;
background: rgba(255, 255, 255, 0.9);
padding: 16px;
-webkit-backdrop-filter: blur(20px);
@media print {
display: none;
}
${breakpoint('tablet')`
left: auto;
padding: ${layout.vpadding} ${layout.hpadding} 8px 8px;
`};
`;
export default Actions;

View File

@ -1,6 +1,7 @@
// @flow
import React from 'react';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
type Props = {
children?: React.Element<any>,
@ -8,7 +9,11 @@ type Props = {
const Container = styled.div`
width: 100%;
padding: 60px;
padding: 60px 20px;
${breakpoint('tablet')`
padding: 60px;
`};
`;
const Content = styled.div`

View File

@ -157,6 +157,7 @@ const SwatchInset = styled(Flex)`
const StyledOutline = styled(Outline)`
padding: 5px;
flex-wrap: wrap;
strong {
font-weight: 500;

View File

@ -4,8 +4,10 @@ import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { Value, Change } from 'slate';
import { Editor } from 'slate-react';
import type { SlateNodeProps, Plugin } from './types';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import keydown from 'react-keydown';
import type { SlateNodeProps, Plugin } from './types';
import getDataTransferFiles from 'utils/getDataTransferFiles';
import Flex from 'shared/components/Flex';
import ClickablePadding from './components/ClickablePadding';
@ -20,7 +22,6 @@ import renderMark from './marks';
import createRenderNode from './nodes';
import schema from './schema';
import { isModKey } from './utils';
import styled from 'styled-components';
type Props = {
text: string,
@ -227,9 +228,15 @@ class MarkdownEditor extends Component {
}
const MaxWidth = styled(Flex)`
margin: 0 60px;
max-width: 46em;
padding: 0 20px;
max-width: 100vw;
height: 100%;
${breakpoint('tablet')`
padding: 0;
margin: 0 60px;
max-width: 46em;
`};
`;
const Header = styled(Flex)`

View File

@ -1,5 +1,7 @@
// @flow
import React, { Component } from 'react';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { Editor } from 'slate-react';
@ -7,7 +9,6 @@ import { Block } from 'slate';
import { List } from 'immutable';
import { color } from 'shared/styles/constants';
import headingToSlug from '../headingToSlug';
import styled from 'styled-components';
type Props = {
editor: Editor,
@ -91,6 +92,7 @@ class Contents extends Component {
}
const Wrapper = styled.div`
display: none;
position: fixed;
right: 0;
top: 150px;
@ -99,6 +101,10 @@ const Wrapper = styled.div`
@media print {
display: none;
}
${breakpoint('tablet')`
display: block;
`};
`;
const Anchor = styled.a`

View File

@ -94,6 +94,7 @@ const Caption = styled.input`
text-align: center;
width: 100%;
outline: none;
background: none;
&::placeholder {
color: ${color.slate};

View File

@ -0,0 +1,12 @@
// @flow
import React from 'react';
import Icon from './Icon';
import type { Props } from './Icon';
export default function MenuIcon(props: Props) {
return (
<Icon {...props}>
<path d="M4.99992841,5.99992841 L19.0000716,5.99992841 C19.5523168,5.99992841 20,6.4476116 20,6.99985681 C20,7.55210202 19.5523168,7.99978522 19.0000716,7.99978522 L4.99992841,7.99978522 C4.4476832,7.99978522 4,7.55210202 4,6.99985681 C4,6.4476116 4.4476832,5.99992841 4.99992841,5.99992841 Z M4.99992841,15.9992125 L19.0000716,15.9992125 C19.5523168,15.9992125 20,16.4468957 20,16.9991409 C20,17.5513861 19.5523168,17.9990693 19.0000716,17.9990693 L4.99992841,17.9990693 C4.4476832,17.9990693 4,17.5513861 4,16.9991409 C4,16.4468957 4.4476832,15.9992125 4.99992841,15.9992125 Z M4.99992841,10.9995704 L19.0000716,10.9995704 C19.5523168,10.9995704 20,11.4472536 20,11.9994988 C20,12.5517441 19.5523168,12.9994273 19.0000716,12.9994273 L4.99992841,12.9994273 C4.4476832,12.9994273 4,12.5517441 4,11.9994988 C4,11.4472536 4.4476832,10.9995704 4.99992841,10.9995704 Z" />
</Icon>
);
}

View File

@ -4,6 +4,7 @@ import { Switch, Route, withRouter } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import { observer, inject } from 'mobx-react';
import keydown from 'react-keydown';
import Analytics from 'shared/components/Analytics';
@ -122,12 +123,16 @@ const Container = styled(Flex)`
`;
const Content = styled(Flex)`
margin-left: ${props => (props.editMode ? 0 : layout.sidebarWidth)};
margin: 0;
transition: margin-left 200ms ease-in-out;
@media print {
margin-left: 0;
margin: 0;
}
${breakpoint('tablet')`
margin-left: ${props => (props.editMode ? 0 : layout.sidebarWidth)};
`};
`;
export default withRouter(inject('user', 'auth', 'ui', 'documents')(Layout));

View File

@ -2,6 +2,7 @@
import React from 'react';
import { observer } from 'mobx-react';
import styled, { injectGlobal } from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import ReactModal from 'react-modal';
import { color } from 'shared/styles/constants';
import { fadeAndScaleIn } from 'shared/styles/animations';
@ -80,14 +81,19 @@ const StyledModal = styled(ReactModal)`
const Close = styled.a`
position: fixed;
top: 3rem;
right: 3rem;
top: 16px;
right: 16px;
opacity: 0.5;
color: ${color.text};
&:hover {
opacity: 1;
}
${breakpoint('tablet')`
top: 3rem;
right: 3rem;
`};
`;
export default observer(Modal);

View File

@ -0,0 +1,83 @@
// @flow
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import { observer, inject } from 'mobx-react';
import Flex from 'shared/components/Flex';
import AccountMenu from 'menus/AccountMenu';
import Sidebar, { Section } from './Sidebar';
import Scrollable from 'components/Scrollable';
import HomeIcon from 'components/Icon/HomeIcon';
import SearchIcon from 'components/Icon/SearchIcon';
import StarredIcon from 'components/Icon/StarredIcon';
import Collections from './components/Collections';
import SidebarLink from './components/SidebarLink';
import HeaderBlock from './components/HeaderBlock';
import AuthStore from 'stores/AuthStore';
import UiStore from 'stores/UiStore';
type Props = {
history: Object,
location: Location,
auth: AuthStore,
ui: UiStore,
};
@observer
class MainSidebar extends Component {
props: Props;
handleCreateCollection = () => {
this.props.ui.setActiveModal('collection-new');
};
handleEditCollection = () => {
this.props.ui.setActiveModal('collection-edit');
};
render() {
const { auth } = this.props;
const { user, team } = auth;
if (!user || !team) return;
return (
<Sidebar>
<AccountMenu
label={
<HeaderBlock
subheading={user.name}
teamName={team.name}
logoUrl={team.avatarUrl}
/>
}
/>
<Flex auto column>
<Scrollable>
<Section>
<SidebarLink to="/dashboard" icon={<HomeIcon />}>
Home
</SidebarLink>
<SidebarLink to="/search" icon={<SearchIcon />}>
Search
</SidebarLink>
<SidebarLink to="/starred" icon={<StarredIcon />}>
Starred
</SidebarLink>
</Section>
<Section>
<Collections
history={this.props.history}
location={this.props.location}
onCreateCollection={this.handleCreateCollection}
/>
</Section>
</Scrollable>
</Flex>
</Sidebar>
);
}
}
export default withRouter(inject('user', 'auth', 'ui')(MainSidebar));

View File

@ -1,12 +1,9 @@
// @flow
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import styled from 'styled-components';
import { observer, inject } from 'mobx-react';
import Flex from 'shared/components/Flex';
import { color, layout } from 'shared/styles/constants';
import Sidebar, { Section } from './Sidebar';
import Scrollable from 'components/Scrollable';
import ProfileIcon from 'components/Icon/ProfileIcon';
import SettingsIcon from 'components/Icon/SettingsIcon';
@ -19,12 +16,11 @@ import AuthStore from 'stores/AuthStore';
type Props = {
history: Object,
location: Location,
auth: AuthStore,
};
@observer
class Sidebar extends Component {
class SettingsSidebar extends Component {
props: Props;
returnToDashboard = () => {
@ -36,7 +32,7 @@ class Sidebar extends Component {
if (!team) return;
return (
<Container column>
<Sidebar>
<HeaderBlock
subheading="◄ Return to Dashboard"
teamName={team.name}
@ -69,26 +65,9 @@ class Sidebar extends Component {
</Section>
</Scrollable>
</Flex>
</Container>
</Sidebar>
);
}
}
const Container = styled(Flex)`
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: ${layout.sidebarWidth};
background: ${color.smoke};
transition: left 200ms ease-in-out;
`;
const Section = styled(Flex)`
flex-direction: column;
margin: 24px 0;
padding: 0 24px;
position: relative;
`;
export default withRouter(inject('auth')(Sidebar));
export default inject('auth')(SettingsSidebar);

View File

@ -3,23 +3,18 @@ import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import { observer, inject } from 'mobx-react';
import Flex from 'shared/components/Flex';
import { color, layout } from 'shared/styles/constants';
import AccountMenu from 'menus/AccountMenu';
import Scrollable from 'components/Scrollable';
import HomeIcon from 'components/Icon/HomeIcon';
import SearchIcon from 'components/Icon/SearchIcon';
import StarredIcon from 'components/Icon/StarredIcon';
import Collections from './components/Collections';
import SidebarLink from './components/SidebarLink';
import HeaderBlock from './components/HeaderBlock';
import CloseIcon from 'components/Icon/CloseIcon';
import MenuIcon from 'components/Icon/MenuIcon';
import AuthStore from 'stores/AuthStore';
import UiStore from 'stores/UiStore';
type Props = {
children: React.Element<any>,
history: Object,
location: Location,
auth: AuthStore,
@ -30,53 +25,32 @@ type Props = {
class Sidebar extends Component {
props: Props;
handleCreateCollection = () => {
this.props.ui.setActiveModal('collection-new');
componentWillReceiveProps = (nextProps: Props) => {
if (this.props.location !== nextProps.location) {
this.props.ui.hideMobileSidebar();
}
};
handleEditCollection = () => {
this.props.ui.setActiveModal('collection-edit');
toggleSidebar = () => {
this.props.ui.toggleMobileSidebar();
};
render() {
const { auth, ui } = this.props;
const { user, team } = auth;
if (!user || !team) return;
const { children, ui } = this.props;
return (
<Container column editMode={ui.editMode}>
<AccountMenu
label={
<HeaderBlock
subheading={user.name}
teamName={team.name}
logoUrl={team.avatarUrl}
/>
}
/>
<Flex auto column>
<Scrollable>
<Section>
<SidebarLink to="/dashboard" icon={<HomeIcon />}>
Home
</SidebarLink>
<SidebarLink to="/search" icon={<SearchIcon />}>
Search
</SidebarLink>
<SidebarLink to="/starred" icon={<StarredIcon />}>
Starred
</SidebarLink>
</Section>
<Section>
<Collections
history={this.props.history}
location={this.props.location}
onCreateCollection={this.handleCreateCollection}
/>
</Section>
</Scrollable>
</Flex>
<Container
editMode={ui.editMode}
mobileSidebarVisible={ui.mobileSidebarVisible}
column
>
<Toggle
onClick={this.toggleSidebar}
mobileSidebarVisible={ui.mobileSidebarVisible}
>
{ui.mobileSidebarVisible ? <CloseIcon /> : <MenuIcon />}
</Toggle>
{children}
</Container>
);
}
@ -87,21 +61,41 @@ const Container = styled(Flex)`
top: 0;
bottom: 0;
left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)};
width: ${layout.sidebarWidth};
width: 100%;
background: ${color.smoke};
transition: left 200ms ease-in-out;
margin-left: ${props => (props.mobileSidebarVisible ? 0 : '-100%')};
z-index: 1;
@media print {
display: none;
left: 0;
}
${breakpoint('tablet')`
width: ${layout.sidebarWidth};
margin: 0;
`};
`;
const Section = styled(Flex)`
export const Section = styled(Flex)`
flex-direction: column;
margin: 24px 0;
padding: 0 24px;
position: relative;
`;
export default withRouter(inject('user', 'auth', 'ui')(Sidebar));
const Toggle = styled.a`
position: fixed;
top: 0;
left: ${props => (props.mobileSidebarVisible ? 'auto' : 0)};
right: ${props => (props.mobileSidebarVisible ? 0 : 'auto')};
z-index: 1;
margin: 16px;
${breakpoint('tablet')`
display: none;
`};
`;
export default withRouter(inject('ui')(Sidebar));

View File

@ -1,3 +1,3 @@
// @flow
import Sidebar from './Sidebar';
import Sidebar from './Main';
export default Sidebar;

View File

@ -33,7 +33,11 @@ class Toast extends Component {
}
render() {
const { type, onRequestClose, message } = this.props;
const { type, onRequestClose } = this.props;
const message =
typeof this.props.message === 'string'
? this.props.message
: this.props.message.toString();
return (
<Container onClick={onRequestClose} type={type}>

View File

@ -41,7 +41,7 @@ class Members extends Component {
{this.props.settings.members && (
<MemberList column>
{this.props.settings.members.map(member => (
<Member auto justify="space-between">
<Member key={member.id} justify="space-between" auto>
<Flex>
<Avatar src={member.avatarUrl} />
<UserName>

View File

@ -10,6 +10,7 @@ class UiStore {
@observable activeCollectionId: ?string;
@observable progressBarVisible: boolean = false;
@observable editMode: boolean = false;
@observable mobileSidebarVisible: boolean = false;
/* Actions */
@action
@ -65,6 +66,16 @@ class UiStore {
disableProgressBar() {
this.progressBarVisible = false;
}
@action
toggleMobileSidebar() {
this.mobileSidebarVisible = !this.mobileSidebarVisible;
}
@action
hideMobileSidebar() {
this.mobileSidebarVisible = false;
}
}
export default UiStore;

View File

@ -124,10 +124,6 @@ const Highlights = styled(Grid)`
const Features = styled(Grid)`
padding: 0 2em;
overflow: hidden;
${breakpoint('mobile')`
flex-direction
`};
`;
const Feature = styled(Grid.Unit)`