Commit 5afd0ec6 authored by Giannis Kopanas's avatar Giannis Kopanas

Merge branch 'styled-components' into 'master'

Use styled components and new theming

See merge request !124
parents 70a3f54d 5974e83e
Pipeline #4970 passed with stages
in 5 minutes and 15 seconds
......@@ -9,7 +9,7 @@
"dist"
],
"dependencies": {
"@pubsweet/ui": "^2.0.0",
"@pubsweet/ui": "3.0.0",
"classnames": "^2.2.5",
"prop-types": "^15.5.10",
"react": "^16.2.0",
......@@ -18,7 +18,6 @@
"react-router-dom": "^4.2.2",
"recompose": "^0.26.0",
"redux": "^3.6.0",
"xpub-bootstrap": "^0.0.2",
"xpub-journal": "^0.0.2"
},
"peerDependencies": {
......
......@@ -5,20 +5,17 @@ import { connect } from 'react-redux'
import { AppBar } from '@pubsweet/ui'
import { withJournal } from 'xpub-journal'
import 'xpub-bootstrap'
import actions from 'pubsweet-client/src/actions'
import classes from './App.local.scss'
const App = ({ children, currentUser, journal, logoutUser }) => (
<div className={classes.root}>
<div>
<AppBar
brand={journal.metadata.name}
onLogoutClick={logoutUser}
user={currentUser}
/>
<div className={classes.main}>{children}</div>
<div>{children}</div>
</div>
)
......
.root {
font-family: "Fira Sans", sans-serif;
}
.root a {
text-decoration: none;
}
.main {
margin-top: 50px;
}
......@@ -9,7 +9,7 @@
"dist"
],
"dependencies": {
"@pubsweet/ui": "^2.0.0",
"@pubsweet/ui": "3.0.0",
"classnames": "^2.2.5",
"lodash": "^4.17.4",
"prop-types": "^15.5.10",
......@@ -23,6 +23,7 @@
"react-router-dom": "^4.2.2",
"recompose": "^0.26.0",
"redux": "^3.6.0",
"styled-components": "^2.4.0",
"xpub-connect": "^0.0.2",
"xpub-journal": "^0.0.2",
"xpub-selectors": "^0.0.2",
......
import React from 'react'
import classes from './Dashboard.local.scss'
import { Page, Section, Heading, UploadContainer } from './molecules/Page'
import UploadManuscript from './UploadManuscript'
import withVersion from './withVersion'
import EditorItem from './sections/EditorItem'
......@@ -19,27 +21,25 @@ const Dashboard = ({
reviewerResponse,
uploadManuscript,
}) => (
<div className={classes.root}>
<div className={classes.upload}>
<Page>
<UploadContainer>
<UploadManuscript
conversion={conversion}
uploadManuscript={uploadManuscript}
/>
</div>
</UploadContainer>
{!dashboard.owner.length &&
!dashboard.reviewer.length &&
!dashboard.editor.length && (
<div className={classes.section}>
<div className={classes.empty}>
Nothing to do at the moment. Please upload a document.
</div>
</div>
<UploadContainer>
Nothing to do at the moment. Please upload a document.
</UploadContainer>
)}
{!!dashboard.owner.length && (
<div className={classes.section}>
<div className={classes.heading}>My Submissions</div>
<Section>
<Heading>My Submissions</Heading>
{dashboard.owner.map(project => (
<OwnerItemWithVersion
deleteProject={() =>
......@@ -52,12 +52,12 @@ const Dashboard = ({
project={project}
/>
))}
</div>
</Section>
)}
{!!dashboard.reviewer.length && (
<div className={classes.section}>
<div className={classes.heading}>To review</div>
<Section>
<Heading>To review</Heading>
{dashboard.reviewer.map(project => (
<ReviewerItemWithVersion
currentUser={currentUser}
......@@ -66,12 +66,12 @@ const Dashboard = ({
reviewerResponse={reviewerResponse}
/>
))}
</div>
</Section>
)}
{!!dashboard.editor.length && (
<div className={classes.section}>
<div className={classes.heading}>My Manuscripts</div>
<Section>
<Heading>My Manuscripts</Heading>
{dashboard.editor.map(project => (
<EditorItemWithVersion
AssignEditor={AssignEditor}
......@@ -79,9 +79,9 @@ const Dashboard = ({
project={project}
/>
))}
</div>
</Section>
)}
</div>
</Page>
)
export default Dashboard
.root {
margin: auto;
max-width: 60em;
}
.upload {
display: flex;
justify-content: center;
}
.section {
margin: 3em 0;
}
.heading {
color: var(--color-primary);
font-family: Vollkorn, serif;
font-size: 1.6em;
// margin: 4em 0 2em;
margin: 1em 0 1em;
text-transform: uppercase;
}
.empty {
display: flex;
justify-content: center;
}
.section:not(:last-of-type) {
margin-bottom: 70px;
}
a {
color: var(--color-primary);
}
......@@ -3,6 +3,7 @@ import Enzyme, { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Dashboard from './Dashboard'
import { Section, Heading, UploadContainer } from './molecules/Page'
// this should be elsewhere
Enzyme.configure({ adapter: new Adapter() })
......@@ -12,7 +13,7 @@ jest.mock('config', () => ({ 'pubsweet-client': {} }))
const getProjects = section =>
section
.children()
.not('.heading')
.not(Heading)
.map(c => c.props().project)
describe('Dashboard', () => {
......@@ -39,8 +40,8 @@ describe('Dashboard', () => {
it('shows a message when there are no projects', () => {
const dashboard = makeWrapper()
expect(dashboard.find('.empty')).toHaveLength(1)
expect(dashboard.find('.heading')).toHaveLength(0)
expect(dashboard.find(UploadContainer)).toHaveLength(2)
expect(dashboard.find(Section)).toHaveLength(0)
})
it('shows a list of projects submitted by the current user', () => {
......@@ -50,7 +51,7 @@ describe('Dashboard', () => {
})
expect(dashboard.find('.empty')).toHaveLength(0)
const section = dashboard.find('.section')
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
......@@ -61,8 +62,8 @@ describe('Dashboard', () => {
dashboard: { reviewer: [project] },
})
expect(dashboard.find('.empty')).toHaveLength(0)
const section = dashboard.find('.section')
expect(dashboard.find(UploadContainer)).toHaveLength(1)
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
......@@ -73,8 +74,8 @@ describe('Dashboard', () => {
dashboard: { editor: [project] },
})
expect(dashboard.find('.empty')).toHaveLength(0)
const section = dashboard.find('.section')
expect(dashboard.find(UploadContainer)).toHaveLength(1)
const section = dashboard.find(Section)
expect(section).toHaveLength(1)
expect(getProjects(section)).toEqual([project])
})
......
......@@ -7,10 +7,13 @@ import thunk from 'redux-thunk'
import Enzyme, { mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { ThemeProvider } from 'styled-components'
import { reducers } from 'pubsweet-client'
import conversion from '../redux/conversion'
import DashboardPage from './DashboardPage'
import { Section, UploadContainer } from './molecules/Page'
// this should be elsewhere
Enzyme.configure({ adapter: new Adapter() })
......@@ -47,15 +50,18 @@ describe('DashboardPage', () => {
const store = mockStore()
const page = mount(
<MemoryRouter>
<Provider store={store}>
<DashboardPage />
</Provider>
<ThemeProvider theme={{ colorPrimary: 'blue' }}>
<Provider store={store}>
<DashboardPage />
</Provider>
</ThemeProvider>
</MemoryRouter>,
)
setImmediate(() => {
page.update()
expect(page.find('.empty')).toHaveLength(1)
expect(page.find(UploadContainer)).toHaveLength(2)
expect(page.find(Section)).toHaveLength(0)
done()
})
})
......
import React from 'react'
import classes from './EmptySubmissions.local.scss'
const EmptySubmissions = () => (
<div className={classes.root}>
<div>You haven&apos;t submitted any manuscripts yet.</div>
</div>
)
export default EmptySubmissions
.root {
color: grey;
font-family: var(--font-author);
font-size: 1.2em;
font-style: italic;
text-align: center;
}
A message that is displayed when the current user has no submissions.
```js
<EmptySubmissions/>
```
import React from 'react'
import { Link } from 'react-router-dom'
import { Link } from '@pubsweet/ui'
const projectUrl = ({ project, version, page, id }) => {
const parts = []
......
import React from 'react'
import styled from 'styled-components'
import { compose, withProps } from 'recompose'
import { groupBy } from 'lodash'
import { withJournal } from 'xpub-journal'
import { Badge } from '@pubsweet/ui'
import classes from './Reviews.local.scss'
import { Badge, th } from '@pubsweet/ui'
const Root = styled.div`
display: inline-flex;
justify-content: flex-end;
margin-bottom: 0.6em;
margin-top: 0.3em;
padding-left: 1.5em;
font-family: ${th('fontReviewer')};
font-size: 0.9em;
`
const BadgeContainer = styled.span`
&:not(:last-child) {
margin-right: 10px;
}
`
const Reviews = ({ reviews, journal }) => (
<div className={classes.root}>
<Root>
{journal.reviewStatus.map(status => (
<span className={classes.badge} key={status}>
<BadgeContainer key={status}>
<Badge
count={reviews[status] ? reviews[status].length : 0}
label={status}
/>
</span>
</BadgeContainer>
))}
</div>
</Root>
)
export default compose(
......
.badge:not(:last-child) {
margin-right: 10px;
}
.root {
font-family: var(--font-reviewer);
}
import React from 'react'
import classes from './Status.local.css'
import styled from 'styled-components'
import { th } from '@pubsweet/ui'
// TODO: move labels to journal config
......@@ -13,8 +14,10 @@ const labels = {
revising: 'Under Revision',
}
const Status = ({ status }) => (
<div className={classes.root}>{labels[status] || 'Unsubmitted'}</div>
)
const Root = styled.div`
color: ${th('colorPrimary')};
`
const Status = ({ status }) => <Root>{labels[status] || 'Unsubmitted'}</Root>
export default Status
.root {
color: var(--color-primary);
text-transform: uppercase;
}
import React, { Component } from 'react'
import styled, { keyframes, withTheme } from 'styled-components'
import Dropzone from 'react-dropzone'
import classnames from 'classnames'
import { Icon } from '@pubsweet/ui'
import classes from './UploadManuscript.local.scss'
import { Icon, th } from '@pubsweet/ui'
const isIdle = conversion => !(conversion.converting || conversion.error)
const StyledDropzone = styled(Dropzone)`
border: none;
cursor: pointer;
display: inline-block;
`
const StatusIcon = withTheme(({ children, theme }) => (
<Icon color={theme.colorPrimary}>{children}</Icon>
))
const Status = styled.div`
align-items: center;
color: ${th('colorPrimary')};
display: inline-flex;
`
const StatusIdle = Status.extend.attrs({
children: () => <StatusIcon>plus_circle</StatusIcon>,
})``
const spin = keyframes`
0% {
transform: rotate(0deg);
transform-origin: 50% 50%;
}
100% {
transform: rotate(360deg);
transform-origin: 50% 50%;
}
`
const StatusConverting = Status.extend.attrs({
children: () => <StatusIcon>plus_circle</StatusIcon>,
})`
&:hover {
cursor: wait;
}
line {
stroke-linejoin: round;
}
circle {
animation: ${spin} 2s infinite linear;
stroke-dasharray: 16;
stroke-dashoffset: 0;
stroke-linejoin: round;
}
`
const StatusError = Status.extend.attrs({
children: () => <StatusIcon>plus_circle</StatusIcon>,
})`
color: ${th('colorDanger')};
font-size: 1.5em;
font-style: italic;
font-weight: 400;
.icon circle {
display: none;
}
.icon line {
stroke: ${th('colorDanger')};
transform: rotate(45deg) scale(2.8);
transform-origin: 50% 50%;
}
`
const dash = keyframes`
from {
stroke-dashoffset: -100;
}
to {
stroke-dashoffset: 0;
}
`
const StatusCompleted = Status.extend.attrs({
children: () => <StatusIcon>check_circle</StatusIcon>,
})`
polyline {
animation: ${dash} 1.35s linear;
stroke-dasharray: 100;
stroke-dashoffset: 0;
}
path {
animation: ${dash} 0.75s linear;
stroke-dasharray: 100;
stroke-dashoffset: 0;
}
`
const Root = styled.div`
display: flex;
font-weight: 200;
padding-bottom: 10px;
padding-top: 10px;
&:hover ${StatusIdle} {
circle {
fill: ${th('colorPrimary')};
stroke: ${th('colorPrimary')};
}
line {
stroke: white;
}
}
`
const Main = styled.div`
margin-left: 10px;
`
const Error = styled.div`
color: ${th('colorDanger')};
font-size: 1.5em;
font-style: italic;
font-weight: 400;
`
const Info = styled.div`
color: ${th('colorPrimary')};
font-size: 2em;
font-weight: 400;
text-transform: uppercase;
`
class UploadManuscript extends Component {
constructor(props) {
......@@ -45,42 +174,46 @@ class UploadManuscript extends Component {
}, 3000)
}
get status() {
if (this.state.completed) {
return 'completed'
}
if (this.state.error) {
return 'error'
}
if (this.props.conversion.converting) {
return 'converting'
}
return 'idle'
}
render() {
const { uploadManuscript, conversion } = this.props
return (
<Dropzone
<StyledDropzone
accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
className={classes.dropzone}
onDrop={uploadManuscript}
>
<div className={classes.root}>
<div
className={classnames({
[classes.idle]: isIdle(conversion),
[classes.converting]: conversion.converting,
[classes.error]: this.state.error,
})}
>
<span className={classes.icon}>
<Icon color="var(--color-primary)">
{this.state.completed ? 'check_circle' : 'plus_circle'}
</Icon>
</span>
</div>
<div className={classes.main}>
<Root>
{this.status === 'completed' && <StatusCompleted />}
{this.status === 'error' && <StatusError />}
{this.status === 'converting' && <StatusConverting />}
{this.status === 'idle' && <StatusIdle />}
<Main>
{this.state.error ? (
<div className={classes.error}>{conversion.error.message}</div>
<Error>{conversion.error.message}</Error>
) : (
<div className={classes.info}>
<Info>
{this.state.completed
? 'Submission created'
: 'Create submission'}
</div>
</Info>
)}
</div>
</div>
</Dropzone>
</Main>
</Root>
</StyledDropzone>
)
}
}
......
.dropzone {
border: none;
cursor: pointer;
display: inline-block;
}
.root {
display: flex;
font-weight: 200;
padding-bottom: 10px;
padding-top: 10px;
}
.icon {
align-items: center;
color: var(--color-primary);
display: inline-flex;
height: 100%;
}
.converting {
&:hover {
cursor: wait;
}
line {