Skip to content
Snippets Groups Projects
Commit 38cfc8e9 authored by Tamlyn Rhodes's avatar Tamlyn Rhodes
Browse files

Merge xpub-ui from xpub monorepo into pubsweet monorepo as pubsweet-ui

parents 150c0e42 5cd5e486
No related branches found
No related tags found
No related merge requests found
Showing
with 728 additions and 0 deletions
{
"globals": {
"initialState": true,
"state": false,
"setState": false
}
}
CSS variables are used to define the theme's color scheme.
## Brand colors
`--color-primary`
```js
<div style={{ color: 'var(--color-primary)' }}>
{faker.lorem.sentence(5)}
</div>
```
## Colors for interactions
`--color-danger`
```js
<div style={{ color: 'var(--color-danger)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--color-valid`
```js
<div style={{ color: 'var(--color-valid)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--color-warning`
```js
<div style={{ color: 'var(--color-warning)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--color-pending`
```js
<div style={{ color: 'var(--color-pending)' }}>
{faker.lorem.sentence(5)}
</div>
```
CSS variables are used to define font families.
`--font-author`
```js
<div style={{ fontFamily: 'var(--font-author)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--font-reviewer`
```js
<div style={{ fontFamily: 'var(--font-reviewer)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--font-interface`
```js
<div style={{ fontFamily: 'var(--font-interface)' }}>
{faker.lorem.sentence(5)}
</div>
```
`--font-mono`
```js
<div style={{ fontFamily: 'var(--font-mono)' }}>
{faker.lorem.sentence(5)}
</div>
```
{
"name": "pubsweet-ui",
"version": "0.0.2",
"files": [
"docs",
"dist",
"src"
],
"main": "src",
"jsnext:main": "src",
"dependencies": {
"babel-jest": "^21.2.0",
"classnames": "^2.2.5",
"enzyme": "^3.2.0",
"enzyme-adapter-react-15": "^1.0.5",
"lodash": "^4.17.4",
"humps": "^2.0.1",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-feather": "^1.0.7",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2",
"react-tag-autocomplete": "^5.4.1",
"recompose": "^0.26.0",
"redux": "^3.6.0",
"redux-form": "^7.0.3"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"css-loader": "^0.28.4",
"faker": "^4.1.0",
"file-loader": "^1.1.5",
"identity-obj-proxy": "^3.0.0",
"jest": "^21.2.1",
"node-sass": "^4.5.3",
"react-styleguidist": "^6.0.8",
"react-test-renderer": "^15.6.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.0",
"webpack": "^3.8.1",
"webpack-node-externals": "^1.6.0",
"xpub-styleguide": "^0.0.2"
},
"jest": {
"moduleNameMapper": {
"\\.s?css$": "identity-obj-proxy"
},
"setupTestFrameworkScriptFile": "<rootDir>/test/setup/enzyme.js",
"transform": {
"\\.js$": "<rootDir>/test/config/transform.js"
}
},
"scripts": {
"styleguide": "styleguidist server",
"styleguide:build": "styleguidist build",
"clean": "rimraf dist",
"prebuild": "npm run clean && npm run lint",
"build": "webpack --progress --profile",
"test": "jest",
"test:watch": "npm test -- --watch",
"test:cover": "npm test -- --coverage",
"test:u": "npm test -- --updateSnapshot"
}
}
import React from 'react'
import Icon from './Icon'
import classes from './Attachment.local.scss'
const Attachment = ({ value }) => (
<a download={value.name} href={value.url}>
<span className={classes.icon}>
<Icon color="var(--color-primary)">paperclip</Icon>
</span>
<span className={classes.filename}>{value.name}</span>
</a>
)
export default Attachment
.icon {
color: var(--color-primary);
display: inline-flex;
margin-right: 10px;
}
.filename {
font-size: 0.7em;
height: 2em;
max-width: 25ch;
overflow-wrap: break-word;
padding: 0;
}
A file attached to a note.
```js
const value = {
name: faker.system.commonFileName(),
url: faker.internet.url()
};
<Attachment value={value}/>
```
import React from 'react'
import classes from './Avatar.local.scss'
const Avatar = ({ status, width, height, reviewerLetter }) => {
const classValue =
status && classes[status.toLowerCase()] ? status.toLowerCase() : 'default'
return (
<svg
className={classes[classValue]}
height={height || '70'}
viewBox={`0 0 ${width ? width + 5 : '105'} ${height || '70'}`}
width={width || '100'}
xmlns="http://www.w3.org/2000/svg"
>
<path
className={classes.persona}
d=" M 47.666 50.14 C 44.947 49 41.588 47.535 41.588 46.395 L 41.588 39.07 C 45.587 35.977 47.986 31.093 47.986 26.047 L 47.986 16.279 C 47.986 7.326 40.788 0 31.991 0 C 23.193 0 15.995 7.326 15.995 16.279 L 15.995 26.047 C 15.995 31.093 18.395 36.14 22.393 39.07 L 22.393 46.395 C 22.393 47.372 19.034 48.837 16.315 50.14 C 9.757 52.907 0 57.14 0 68.372 L 0 70 L 63.981 70 L 63.981 68.372 C 63.981 57.14 54.224 52.907 47.666 50.14 Z "
/>
<path
className={classes.check}
d=" M 60.106 37.467 C 59.299 36.645 58.895 35.617 58.895 34.486 C 58.895 33.458 59.299 32.43 60.106 31.608 C 60.813 30.888 61.823 30.375 62.934 30.375 C 64.045 30.375 65.055 30.888 65.762 31.608 L 74.246 40.242 L 93.132 21.021 C 93.839 20.301 94.95 19.89 95.96 19.89 C 97.071 19.89 98.081 20.301 98.788 21.021 C 99.596 21.843 100 22.871 100 24.002 C 100 25.03 99.596 26.057 98.788 26.88 L 74.246 51.857 L 60.106 37.467 Z "
/>
<path
className={classes.x}
d="M 70.964 37.518 L 62.025 46.615 C 61.217 47.54 60.712 48.671 60.712 49.904 C 60.712 51.138 61.217 52.268 62.025 53.091 C 62.934 54.016 64.045 54.427 65.257 54.427 C 66.469 54.427 67.58 54.016 68.388 53.091 L 77.326 43.994 L 86.265 53.091 C 87.173 54.016 88.284 54.427 89.496 54.427 C 90.708 54.427 91.819 54.016 92.627 53.091 C 93.536 52.268 93.94 51.138 93.94 49.904 C 93.94 48.671 93.536 47.54 92.627 46.615 L 83.689 37.518 L 92.627 28.422 C 93.536 27.599 93.94 26.469 93.94 25.235 C 93.94 24.002 93.536 22.871 92.627 21.946 C 91.819 21.124 90.708 20.61 89.496 20.61 C 88.284 20.61 87.173 21.124 86.265 21.946 L 77.326 31.043 L 68.388 21.946 C 67.58 21.124 66.469 20.61 65.257 20.61 C 64.045 20.61 62.934 21.124 62.025 21.946 C 61.217 22.871 60.712 24.002 60.712 25.235 C 60.712 26.469 61.217 27.599 62.025 28.422 L 70.964 37.518 Z"
/>
<path
className={classes['question-mark']}
d=" M 79.674 23.203 L 79.674 23.203 Q 83.397 23.203 85.424 25.077 L 85.424 25.077 L 85.424 25.077 Q 87.451 26.95 87.451 29.771 L 87.451 29.771 L 87.451 29.771 Q 87.451 31.75 86.728 33.14 L 86.728 33.14 L 86.728 33.14 Q 86.003 34.529 85.011 35.371 L 85.011 35.371 L 85.011 35.371 Q 84.018 36.214 82.404 37.224 L 82.404 37.224 L 82.404 37.224 Q 80.625 38.361 79.798 39.182 L 79.798 39.182 L 79.798 39.182 Q 78.97 40.003 78.97 41.308 L 78.97 41.308 L 78.97 41.308 Q 78.97 41.94 79.094 42.319 L 79.094 42.319 L 72.971 43.287 L 72.971 43.287 Q 72.64 42.024 72.64 41.098 L 72.64 41.098 L 72.64 41.098 Q 72.64 39.203 73.282 37.898 L 73.282 37.898 L 73.282 37.898 Q 73.923 36.593 74.833 35.814 L 74.833 35.814 L 74.833 35.814 Q 75.743 35.035 77.15 34.108 L 77.15 34.108 L 77.15 34.108 Q 78.681 33.056 79.405 32.298 L 79.405 32.298 L 79.405 32.298 Q 80.129 31.54 80.129 30.403 L 80.129 30.403 L 80.129 30.403 Q 80.129 29.603 79.653 29.203 L 79.653 29.203 L 79.653 29.203 Q 79.177 28.803 78.35 28.803 L 78.35 28.803 L 78.35 28.803 Q 76.405 28.803 74.006 31.245 L 74.006 31.245 L 70.282 27.708 L 70.282 27.708 Q 74.171 23.203 79.674 23.203 L 79.674 23.203 Z M 75.371 53.94 L 75.371 53.94 Q 73.84 53.94 72.93 52.951 L 72.93 52.951 L 72.93 52.951 Q 72.02 51.961 72.02 50.445 L 72.02 50.445 L 72.02 50.445 Q 72.02 48.635 73.24 47.33 L 73.24 47.33 L 73.24 47.33 Q 74.461 46.024 76.24 46.024 L 76.24 46.024 L 76.24 46.024 Q 77.77 46.024 78.681 47.014 L 78.681 47.014 L 78.681 47.014 Q 79.591 48.003 79.591 49.561 L 79.591 49.561 L 79.591 49.561 Q 79.591 51.414 78.37 52.677 L 78.37 52.677 L 78.37 52.677 Q 77.15 53.94 75.371 53.94 L 75.371 53.94 Z "
/>
<g transform="matrix(1.01,0,0,1.028,64.651,6.065)">
<text
className={classes['reviewer-number']}
transform="matrix(1,0,0,1,0,46.75)"
>
{reviewerLetter}
</text>
</g>
</svg>
)
}
export default Avatar
figure {
margin: 0 auto 2px;
text-align: center;
width: auto;
}
svg {
height: auto;
max-height: 100vh;
max-width: 100%;
width: auto;
}
.default {
.persona {
display: block;
fill: var(--color-primary);
}
.check {
display: none;
}
.x {
display: none;
}
.question-mark {
display: none;
}
.reviewer-number {
display: none;
}
}
.accepted {
.persona {
display: block;
fill: var(--color-primary);
}
.check {
display: block;
fill: var(--color-primary);
}
.x {
display: none;
}
.question-mark {
display: none;
}
.reviewer-number {
display: none;
}
}
.declined {
.persona {
display: block;
fill: var(--color-danger);
}
.check {
display: none;
}
.x {
display: block;
fill: var(--color-danger);
}
.question-mark {
display: none;
}
.reviewer-number {
display: none;
}
}
.pending {
.persona {
display: block;
fill: var(--color-pending);
}
.check {
display: none;
}
.x {
display: none;
}
.question-mark {
display: block;
fill: var(--color-pending);
}
.reviewer-number {
display: none;
}
}
.submitted {
.persona {
display: block;
fill: var(--color-primary);
}
.check {
display: none;
}
.x {
display: none;
}
.question-mark {
display: none;
}
.reviewer-number {
fill: var(--color-primary);
font-family: 'Fira Sans Condensed', sans-serif;
font-size: 50px;
font-style: normal;
font-weight: 600;
stroke: none;
text-transform: uppercase;
}
}
.fullname {
color: red;
font-family: var(--font-reviewer);
}
A general purpose Avatar element.
```js
const statusFactory = () => {
const statuses = ['Accepted', 'Pending', 'Declined', 'Submitted']
return statuses[Math.floor(Math.random() * statuses.length)]
};
<Avatar status={statusFactory()}/>
```
import React from 'react'
import classes from './Badge.local.scss'
const Badge = ({ count, label, plural }) => (
<span className={classes.root}>
<span className={classes.count}>{count}</span>
<span className={classes.label}>
{plural && count !== 1 ? plural : label}
</span>
</span>
)
export default Badge
.root {
align-items: center;
// background: lightgrey;
background: linear-gradient(#fff 0, #fff 1.1em, grey 1.1em, grey 1.15em, #fff 1.15em, #fff 2em);
color: inherit;
display: inline-flex;
font-size: 0.8rem;
margin-right: 1em;
padding-bottom: 1em;
}
.count {
border-radius: 50%;
color: grey;
font-size: 1em;
font-weight: 600;
padding-right: 0.5em;
text-align: center;
}
.label {
display: inline-block;
padding: 0;
text-shadow: 0.05em 0.05em 0 #fff, -0.05em -0.05em 0 #fff, -0.05em 0.05em 0 #fff, 0.05em -0.05em 0 #fff;
}
A badge that displays a count and a label.
```js
<Badge count={5} label="created"/>
```
A plural form of the label can be provided.
```js
<div>
<Badge count={1} label="thing" plural="things"/>
<Badge count={99} label="thing" plural="things"/>
<Badge count={0} label="thing" plural="things"/>
<Badge count={299} label="thing" plural="things"/>
</div>
```
import React from 'react'
import classnames from 'classnames'
import classes from './Button.local.scss'
const Button = ({
className,
children,
type = 'button',
disabled,
primary,
onClick,
}) => (
<button
className={classnames(className, classes.root, {
[classes.disabled]: disabled,
[classes.primary]: primary,
})}
disabled={disabled}
onClick={onClick}
type={type}
>
{children}
</button>
)
export default Button
.root {
background: #ddd;
border: none;
cursor: pointer;
font-family: var(--font-interface);
font-size: inherit;
letter-spacing: 0.05em;
padding: 10px 20px;
position: relative;
text-transform: uppercase;
}
.root:hover,
.root:focus {
background: #777;
color: white;
outline: 1px solid transparent;
}
// this will be added to the button that need a feedback to the user.
// &::after {
// content: "Saved!";
// top: 20%;
// left: 115%;
// position: absolute;
// background: var(--color-primary);
// color: white;
// padding: 0.1em 0.3em;
// opacity: 0;
// }
.root :active {
transform: scale(0.8);
}
.root ::after {
animation: 1s warning;
opacity: 1;
}
.primary {
background-color: var(--color-primary);
border: 2px solid transparent;
border-bottom: 4px solid var(--color-primary);
color: white;
}
.primary:hover {
background: white;
border: 2px solid var(--color-primary);
border-bottom: 4px solid var(--color-primary);
color: var(--color-primary);
outline: 1px solid transparent;
}
.primary:focus {
background: white;
border: 2px solid var(--color-primary);
border-bottom: 4px solid var(--color-primary);
box-shadow: 0 2px 0 0 var(--color-primary);
color: var(--color-primary);
outline: 1px solid transparent;
}
.disabled {
background: white;
border: 2px solid transparent;
border-bottom: 2px solid #bbb;
color: #bbb;
}
.disabled:hover {
background: transparent;
border: 2px solid transparent;
border-bottom: 2px solid #bbb;
color: #aaa;
cursor: not-allowed;
}
.disabled:hover::after {
color: var(--color-danger);
content: "sorry, this action is not possible";
display: inline;
font-size: 0.9em;
font-style: italic;
left: 115%;
letter-spacing: 0;
opacity: 1;
position: absolute;
text-align: left;
text-transform: lowercase;
top: 30%;
// width: 30ch;
}
.addFile {
background: none;
border: none;
font-style: normal;
letter-spacing: 0;
padding: 0;
text-transform: none;
}
A button.
```js
<Button>Save</Button>
```
A button can be disabled.
```js
<Button disabled>Save</Button>
```
A button can be marked as the "primary" action.
```js
<Button primary>Save</Button>
```
import React from 'react'
import classnames from 'classnames'
import classes from './Checkbox.local.scss'
const Checkbox = ({
inline,
name,
value,
label,
checked,
required,
onChange,
}) => (
<label
className={classnames(classes.root, {
[classes.inline]: inline,
})}
>
<input
checked={checked || false}
className={classes.input}
name={name}
onChange={onChange}
required={required}
type="checkbox"
value={value}
/>
<span>{label}</span>
</label>
)
export default Checkbox
.root {
align-items: center;
display: flex;
font-family: var(--font-author);
font-size: 1em;
font-style: italic;
letter-spacing: 1px;
transition: all 2s;
}
.root.inline {
display: inline-flex;
}
.root.inline:not(:last-child) {
margin-right: 2.7em;
}
.root:not(.inline):not(:last-child) {
margin-bottom: 0.5rem;
}
.root .input {
display: none;
margin-right: 0.25rem;
}
.root span::before {
background-size: 0;
border: 1px solid black; // border-radius: 20px;
content: ' ';
display: inline-block;
height: 9px;
margin-right: 0.3em;
transition: border 0.5s ease, background-size 0.3s ease;
vertical-align: middle;
width: 9px;
}
.root:hover span::before {
//background-size: 100%;
background: var(--color-primary);
box-shadow: inset 1px 1px 0 0 white, inset -1px -1px 0 0 white;
}
.root input:checked + span {
font-weight: 600;
&::before {
background: black;
border: 1px solid black;
box-shadow: inset 1px 1px 0 0 white, inset -1px -1px 0 0 white;
transition: border 0.5s ease, background-size 0.3s ease;
}
}
A checkbox.
```js
initialState = { checked: null };
<Checkbox
name="checkbox"
checked={state.checked}
onChange={event => setState({ checked: event.target.checked })}/>
```
A checked checkbox.
```js
initialState = { checked: true };
<Checkbox
name="checkbox-checked"
checked={state.checked}
onChange={event => setState({ checked: event.target.checked })}/>
```
A checkbox with a label.
```js
initialState = { checked: false };
<Checkbox
name="checkbox-labelled"
checked={state.checked}
label="Foo"
onChange={event => setState({ checked: event.target.checked })}/>
```
import React from 'react'
import classes from './File.local.scss'
const extension = ({ name }) => name.replace(/^.+\./, '')
const File = ({ value }) => (
<div className={classes.root}>
<div className={classes.icon}>
<div className={classes.extension}>{extension(value)}</div>
</div>
<div className={classes.name}>
<a download={value.name} href={value.url}>
{value.name}
</a>
</div>
</div>
)
export default File
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment