diff --git a/packages/component-faraday-ui/package.json b/packages/component-faraday-ui/package.json index 020fd030a03d74398e998b9ed5488b05175e6d9a..a21a5636ee92506df9bbb6b468f6d9e9cafddeb3 100644 --- a/packages/component-faraday-ui/package.json +++ b/packages/component-faraday-ui/package.json @@ -12,5 +12,6 @@ "querystring": "^0.2.0", "react": "^16.4.2", "styled-components": "^3.4.2" - } + }, + "devDependencies": {} } diff --git a/packages/component-faraday-ui/src/AppBar.md b/packages/component-faraday-ui/src/AppBar.md index 49d8caf19e0c34f49c73d564d41d0bee7c07a981..107dbbe75848be2c09398dcf1608fff58059dbf7 100644 --- a/packages/component-faraday-ui/src/AppBar.md +++ b/packages/component-faraday-ui/src/AppBar.md @@ -18,7 +18,7 @@ const autosave = { const HindawiLogo = () => ( <Logo onClick={() => console.log('Hindawi best publish!')} - title="Hindawi" + title="Anca" src="https://upload.wikimedia.org/wikipedia/en/thumb/c/ca/Hindawi.svg/1200px-Hindawi.svg.png" /> ) diff --git a/packages/component-faraday-ui/src/Text.js b/packages/component-faraday-ui/src/Text.js index d8092434a6dc7f35fa449cd1ad801d5344e0ea26..0368f97a32f5e757721c9e39f73ec03f8dfef790 100644 --- a/packages/component-faraday-ui/src/Text.js +++ b/packages/component-faraday-ui/src/Text.js @@ -1,30 +1,31 @@ import React from 'react' -import { has, get } from 'lodash' +import { get } from 'lodash' +import PropTypes from 'prop-types' import { th } from '@pubsweet/ui-toolkit' import styled, { css } from 'styled-components' import { paddingHelper, marginHelper } from './styledHelpers' const textHelper = props => { - if (has(props, 'secondary')) { + if (get(props, 'secondary')) { return css` color: ${th('colorSecondary')}; font-family: ${th('fontReading')}; ` } - if (has(props, 'error')) { + if (get(props, 'error')) { return css` color: ${th('colorError')}; font-family: ${th('fontReading')}; ` } - if (has(props, 'customId')) { + if (get(props, 'customId')) { return css` color: ${th('colorPrimary')}; font-family: ${th('fontInterface')}; ` } - if (has(props, 'labelLine')) { + if (get(props, 'labelLine')) { return css` color: ${th('colorFurnitureHue')}; font-family: ${th('fontInterface')}; @@ -38,7 +39,7 @@ const textHelper = props => { } ` } - if (has(props, 'journal')) { + if (get(props, 'journal')) { return css` color: ${th('colorSecondary')}; font-family: ${th('fontReading')}; @@ -88,7 +89,7 @@ const Root = styled.div` display: flex; ` -export default ({ bullet, children, ...rest }) => +const Text = ({ bullet, children, ...rest }) => bullet ? ( <Root> <Bullet>{'\u2022'}</Bullet> @@ -97,3 +98,38 @@ export default ({ bullet, children, ...rest }) => ) : ( <StyledText {...rest}>{children}</StyledText> ) + +Text.propTypes = { + /** if true */ + secondary: PropTypes.bool, + /** if true then object gets properties */ + error: PropTypes.bool, + /** if true then object gets properties */ + customId: PropTypes.bool, + /** if true then object gets properties */ + labelLine: PropTypes.bool, + /** if true then object gets properties */ + journal: PropTypes.bool, + /** if true thenobject gets properties */ + small: PropTypes.bool, + /** defines how items will be displayed */ + display: PropTypes.string, + /** defines how items will be aligned */ + align: PropTypes.string, + /** defines if there will be a white space */ + whiteSpace: PropTypes.string, +} + +Text.defaultProps = { + secondary: false, + error: false, + customId: false, + labelLine: false, + journal: false, + small: false, + display: 'inline-block', + align: 'start', + whiteSpace: 'initial', +} + +export default Text diff --git a/packages/component-faraday-ui/src/gridItems/Item.js b/packages/component-faraday-ui/src/gridItems/Item.js index 32b29bc614cf063b2639125127f07bf76d54c549..6680ce0f5c4b9b69acf6fdf7372319c15fe8223d 100644 --- a/packages/component-faraday-ui/src/gridItems/Item.js +++ b/packages/component-faraday-ui/src/gridItems/Item.js @@ -1,10 +1,11 @@ import { isNumber } from 'lodash' +import PropTypes from 'prop-types' import styled from 'styled-components' import { marginHelper, paddingHelper } from 'pubsweet-component-faraday-ui' /** @component */ -export default styled.div.attrs({ +const Item = styled.div.attrs({ 'data-test-id': props => props['data-test-id'] || 'item', })` align-items: ${({ alignItems }) => alignItems || 'initial'}; @@ -17,3 +18,26 @@ export default styled.div.attrs({ ${marginHelper}; ${paddingHelper}; ` + +Item.propTypes = { + /** defines how flex items are laid out along the axis */ + alignItems: PropTypes.string, + /** is a sub-property for flexbox that sets flexible length on flexible items */ + flex: PropTypes.number, + /** specifies how flex items are placed in the flex container */ + vertical: PropTypes.bool, + /** sets whether flex items are forced onto one line or can wrap ont multiple ones */ + flexWrap: PropTypes.string, + /** specifies how the browser distributes space between and around items along the axis */ + justify: PropTypes.string, +} + +Item.defaultProps = { + alignItems: 'initial', + flex: 1, + vertical: false, + flexWrap: 'initial', + justify: 'initial', +} + +export default Item diff --git a/packages/component-faraday-ui/src/gridItems/Item.md b/packages/component-faraday-ui/src/gridItems/Item.md index f6f91b4d33f93f53be7c1257d2786a38dadc315b..60aa616a3d7c01496c47fb5f353bf9f529ef062a 100644 --- a/packages/component-faraday-ui/src/gridItems/Item.md +++ b/packages/component-faraday-ui/src/gridItems/Item.md @@ -7,6 +7,14 @@ An item. By default the content is displayed in a row. </Item> ``` +All items are wrapped into a flex container + +```js +<Item flex={1}> + <div>i m the only flex container</div> +</Item> +``` + Displayed in a column. ```js @@ -15,3 +23,33 @@ Displayed in a column. <span>I am at the bottom</span> </Item> ``` + +Items are aligned in their containing block + +```js +<Item alignItems="center" vertical> + <div>I m the first item</div> + <div>I m the second item</div> + <div>I m the third item</div> +</Item> +``` + +Long items wrap on the next row + +```js +<Item flexWrap="wrap"> + <div>wrap us together please, i m first, and i will be the longest to prove my point</div> + <div>wrap us together please, i m second</div> + <div>wrap us together please, i m last</div> +</Item> +``` + +Adds spaces between items + +```js +<Item justify="space-between"> + <div>group us from the left, i m first</div> + <div>group us from the left, i m second</div> + <div>group us from the left, i m last</div> +</Item> +``` diff --git a/packages/component-faraday-ui/src/gridItems/Row.js b/packages/component-faraday-ui/src/gridItems/Row.js index 794ed2b3f00faf6c82f720745b3826eed63bc3f2..4afa71d265da88d1ccbb472762d86790a55427ad 100644 --- a/packages/component-faraday-ui/src/gridItems/Row.js +++ b/packages/component-faraday-ui/src/gridItems/Row.js @@ -1,10 +1,11 @@ import { get } from 'lodash' +import PropTypes from 'prop-types' import styled from 'styled-components' import { heightHelper, marginHelper, paddingHelper } from '../styledHelpers' /** @component */ -export default styled.div.attrs({ +const Row = styled.div.attrs({ 'data-test-id': props => props['data-test-id'] || 'row', })` align-items: ${props => get(props, 'alignItems', 'flex-start')}; @@ -20,3 +21,26 @@ export default styled.div.attrs({ ${marginHelper}; ${paddingHelper}; ` + +Row.propTypes = { + /** defines how flex items are laid out along the axis */ + alignItems: PropTypes.string, + /** defines the background color */ + backgroundColor: PropTypes.string, + /** specifies how the browser distributes space between and around items along the axis */ + flexWrap: PropTypes.string, + /** specifies how the browser distributes space between and around items along the axis */ + justifyContent: PropTypes.string, + /** specifies the height of the item */ + height: PropTypes.string, +} + +Row.defaultProps = { + alignItems: 'flex-start', + bgColor: 'transparent', + flexWrap: 'initial', + justifyContent: 'space-evenly', + height: '100%', +} + +export default Row diff --git a/packages/component-faraday-ui/src/gridItems/Row.md b/packages/component-faraday-ui/src/gridItems/Row.md index 77bcd1717adfaab12885fe3178df888070346276..c397c36e451ca563392a6b28bd5f87eeb13a39cd 100644 --- a/packages/component-faraday-ui/src/gridItems/Row.md +++ b/packages/component-faraday-ui/src/gridItems/Row.md @@ -2,8 +2,57 @@ A row of items. ```js <Row> - <Item>Item 1</Item> - <Item>Item 2</Item> - <div>Item 3</div> + <Item>Item 1</Item> + <Item>Item 2</Item> + <Item>Item 3</Item> </Row> -``` \ No newline at end of file +``` + +Items are aligned in their containing block + +```js +<Row alignItems="flex-start" vertical> + <Item>I m the first item</Item> + <Item>I m the second item</Item> + <Item>I m the third item</Item> +</Row> +``` + +Long items wrap on the next row + +```js +<Row flexWrap="wrap"> + <Item> + wrap us together please, i m first, and i will be the longest to prove my + point + </Item> + <Item>wrap us together please, i m second</Item> + <Item>wrap us together please, i m last</Item> +</Row> +``` + +Adds spaces between items + +```js +<Row justifyContent="space-evenly"> + <Item>group us from the left, i m first</Item> + <Item>group us from the left, i m second</Item> + <Item>group us from the left, i m last</Item> +</Row> +``` + +The height of an item is specified + +```js +<Row height="100%"> + <Item>this is an item</Item> +</Row> +``` + +Adds color to the row + +```js +<Row bgColor="aqua"> + <Item>this is an item</Item> +</Row> +``` diff --git a/packages/component-faraday-ui/src/helpers/helpers.md b/packages/component-faraday-ui/src/helpers/helpers.md new file mode 100644 index 0000000000000000000000000000000000000000..15a652d3ec699c2b98c3f903793c34804a2272ae --- /dev/null +++ b/packages/component-faraday-ui/src/helpers/helpers.md @@ -0,0 +1,297 @@ +## Hindawi Helpers + +_Utility HOCs_ + +* [withCountries](#withcountries) +* [withFetching](#withfetching) +* [withFileDownload](#withfiledownload) +* [withFilePreview](#withfilepreview) +* [withPagination](#withpagination) +* [withRoles](#withroles) +* [withZipDownload](#withzipdownload) + +_HOCs used for files drag and drop_ + +* [withFileSectionDrop](#withfilesectiondrop) +* [withNativeFileDrop](#withnativefiledrop) + +_Utility functions_ + +* [handleError](#handleerror) + +# Utility HOCs + +## withCountries + +Injects `countries` and `countryLabel` as props. + +### withCountries props + +* `countries: [{value: string, label: string}]`: the list of countries +* `countryLabel: (code: string) => string`: get the name of the country with the specified code + +```js +import { Menu } from '@pubsweet/ui' +import { withCountries } from 'pubsweet-component-faraday-ui' + +const Wrapped = ({ countries, countryLabel }) => ( + <div> + <Menu options={countries} placeholder="Select a country" /> + + <span>Selected country: {countryLabel('RO')}</span> + </div> +) + +export default withCountries(Wrapped) +``` + +## withFetching + +Injects `isFetching`, `fetchingError`, `setFetching`, `toggleFetching`, `setError` and `clearError` as props. + +### withFetching props + +* `isFetching: bool`: value representing a pending async operation +* `fetchingError: string`: value representing the error +* `setFetching: (value: bool) => any`: function for setting the `isFetching` value +* `toggleFetching: () => any`: function that toggle the current value of `isFetching` +* `setError: (error: string) => any`: function that sets `fetchingError` +* `clearError: () => any`: function that resets `fetchingError` to it's original value + +```js +import { withFetching } from 'pubsweet-component-faraday-ui' + +const Wrapped = ({ isFetching, fetchingError, setFetching, toggleFetching }) => ( + <div> + {isFetching && <span>I am fetching</span>} + <span>{`The error: ${fetchingError}`</span> + <button onClick={() => setFetching(true)}>Set fetching true</button> + <button onClick={() => setFetching(false)}>Set fetching false</button> + <button onClick={toggleFetching}>Toggle fetching</button> + </div> +) + +export default withFetching(Wrapped) +``` + +## withFileDownload + +Injects `downloadFile` as a prop. + +### withFileDownload props + +* `downloadFile: (file: {id: string, name: string}) => any`: downloads the file specified as a parameter. The wrapped component should have the authentication token in a prop called `token` in order for this to work. + +```js +import { FileItem, withFileDownload } from 'pubsweet-component-faraday-ui' + +const file = { + id: 'myfile', + name: 'myfile.pdf', + size: 100231, +} + +const Wrapped = ({ downloadFile }) => ( + <div> + <FileItem item={file} onDownload={downloadfile} /> + </div> +) + +export default withFileDownload(Wrapped) +``` + +## withFilePreview + +Generate a securized file URL and preview it in a new tab. Injects `previewFile` as a prop. + +This HOC assumes the following props are present on the wrapped component: + +* `getSignedURL: (id: string) => Promise({signedURL: string})`: an async call that returns the securized S3 file url + +### withFilePreviewProps + +* `previewFile: (file: {id: string, ...}) => any`: opens the file preview in a new tab (only possible for PDF files and images) + +```javascript +import { withProps } from 'recompose' +import { FileItem, withFilePreview Wrapped} from 'pubsweet-component-faraday-ui' + +const file = { + id: 'myfile', + name: 'myfile.pdf', + size: 100231, +} + +const Wrapped = ({ previewFile }) => ( + <div> + <FileItem item={file} onPreview={previewFile} /> + </div> +) + +export default withFilePreview(Wrapped) +``` + +## withPagination + +Injects `page`, `itemsPerPage`, `toFirst`, `nextPage`, `toLast`, `prevPage`, `changeItemsPerPage`, `hasMore`, `maxItems` and `paginatedItems` as props. + +### withPagination props + +* `page: number`: the current page +* `itemsPerPage: number`: number of items to be shown per page +* `maxItems: number`: the total number of items +* `hasMore: bool`: if we're not at the last page yet +* `paginatedItems: [any]`: slice of the original items +* `toFirst: () => { page: number }`: go to the first page +* `toLast: () => {page: number}`: go to the last page +* `nextPage: () => {page: number}`: move to the next page +* `prevPage: () => {page: number}`: move to the previous page +* `changeItemsPerPage: e: HTMLInputEvent => {page: number, itemsPerPage: number}`: change the number of items per page + +```js +import { withPagination } from 'pubsweet-component-faraday-ui' + +const Wrapped = ({ page, nextPage, prevPage, paginatedItems, changeItemsPerPage }) => ( + <div> + <span>Page {page}</span> + <button onClick={prevPage}>Prev page</button> + <button onClick={nextPage}>Next page</button> + <input type="text" onChange={changeItemsPerPage} /> + <div> + { + paginatedItems.map(p => <span>{p}<span>) + } + </div> + </div> +) + +export default withPagination(Wrapped) +``` + +## withRoles + +Injects the `roles` array as a prop. The roles are parsed from the journal config files. + +### withRoles props + +* `roles: [{value: string, label: string}]`: an array of user roles + +```js +import { Menu } from '@pubsweet/ui' +import { withRoles } from 'pubsweet-component-faraday-ui' + +const Wrapped = ({ roles }) => <Menu options={roles} /> + +export default withRoles(Wrapped) +``` + +## withZipDownload + +Downloads all the files of a fragment as a zip archive. Injects the `downloadFiles` function as a prop. + +This HOCs assumes the following props are present on the wrapped component: + +* `token: string`: authentication token (used to authorize this request) +* `isReview: bool`: if the user is reviewer +* `fragmentId: string`: id of the fragment whose files we want to download +* `setFetching: (value: bool) => any`: a callback to set a fetching status +* `archiveName: string`: the name of the outputted archive file + +### withZipDownload props + +* `downloadFiles: () => any`: download all the fragment's file as a zip + +# Files drag and drop + +## withFileSectionDrop + +HOC used to provide drop functionality to the `FileSection` component. It's main purpose is to change a file from one list to another. This is usually done in a callback called `changeList` that should be provided to the wrapped component. + +This HOC assumes the wrapped component has the following props: + +* `files: [{id: string, ...}]`: the list of files passed to the wrapped component +* `setError: (error: string) => any`: error setting callback +* `listId: string`: the current list id +* `allowedFileExtensions: [string]`: the allowed files +* `maxFiles: number`: the maximum number of files allowed +* `changeList: (fromListId: string, toListId: string: fileId: string)`: callback called if all the conditions are met (allowed files, number of files, etc) + +```js +import { compose, withHandler, withProps } from 'recompose' +import { FileSection, withFileSectionDrop } from 'pubsweet-component-faraday-ui' + +const Wrapped = compose( + withProps({ + files: [...], + listId: 'CoverLetter', + maxFiles: 3, + allowedFileExtensions: ['pdf'], + }), + withHandlers({ + changeList: () => (fromListId, toListId, fileId) => { + // do the actual change here + } + }), + withFileSectionDrop, +)(FileSection) + +export default Wrapped +``` + +## withNativeFileDrop + +HOC used to provide native file drop functionality to the `FileSection` component. It's purpose is to do something when dragging files from the computer's hard drive into the app. _This HOC allows only single items! Dragging multiple items into the wrapped component will only handle the first item!_ + +This HOC assumes the wrapped component has the following props: + +* `files: [{id: string, ...}]`: the list of files passed to the wrapped component +* `setError: (error: string) => any`: error setting callback +* `allowedFileExtensions: [string]`: the allowed files +* `maxFiles: number`: the maximum number of files allowed +* `onFileDrop: (file: File)`: callback called when a valid file is dropped + +```js +import { compose, withHandler, withProps } from 'recompose' +import { FileSection, withNativeFileDrop } from 'pubsweet-component-faraday-ui' + +const Wrapped = compose( + withProps({ + files: [...], + listId: 'CoverLetter', + maxFiles: 3, + allowedFileExtensions: ['pdf'], + }), + withHandlers({ + onFileDrop: () => file => { + // do something with the dropped file + } + }), + withNativeFileDrop, +)(FileSection) + +export default Wrapped +``` + +# Utility functions + +## handleError + +Function that parses the server error. Calls the passed function with the parsed error. + +Has the following signature: +`(callbackFn: (parsedError) => any) => (e: Error) => any` + +```js +const customErrorLogger = parsedError => + console.error(`This is very handled: ${parsedError}`) + +// point free notation +anAsyncOperation().catch(handleError(customErrorLogger)) + +// can be used besides other function calls + +anAsyncOperation().catch(err => { + setFetching(false) + handleError(customErrorLogger)(err) +}) +``` diff --git a/packages/component-manuscript/src/inviteHandlingEditor/withInviteHandlingEditor.md b/packages/component-manuscript/src/inviteHandlingEditor/withInviteHandlingEditor.md new file mode 100644 index 0000000000000000000000000000000000000000..257c2b2a25142ef6f36953cc9d0f3067036aef95 --- /dev/null +++ b/packages/component-manuscript/src/inviteHandlingEditor/withInviteHandlingEditor.md @@ -0,0 +1,28 @@ +## Hindawi Handling Editor Invite HOC. + +Injects `assignHE`, `revokeHE` and `onHEResponse` handlers as props. + +### withInviteHandlingEditor props + +* `inviteHandlingEditor: object`: namespace containing the following fields: + * `assignHE: function`: sends an invitation to the handling editor. + * `revokeHE: function`: revokes a sent invitation to the handling editor. + * `onHeResponse: function`: handles the handling editor's response. + +```javascript +const EditorInChiefPanel = ({ assignHE, revokeHE }) => ( + <Modal> + <span>Handlin d'Editor</span> + <button onClick={() => assignHE(email, {...modalProps, setFetching})}>Resend Invitation</button> + <button onClick={() => revokeHE(invitationId, {...modalProps, setFetching})}>Cancel Invitation</button> + </Modal> +) + +const HandlingEditorPanel = ({ onHeResponse }) => ( + <Modal> + <span>Accept invitation?</span> + <button onClick={() => onHeResponse(values, {...modalProps, setFetching})}>Yes</button> + <button onClick={() => onHeResponse(values, {...modalProps, setFetching})}>No</button> + </Modal> +) +``` diff --git a/packages/component-manuscript/src/inviteReviewer/withInviteReviewer.md b/packages/component-manuscript/src/inviteReviewer/withInviteReviewer.md new file mode 100644 index 0000000000000000000000000000000000000000..e8d03b883b74aa3f5d3c5170cc594a13a2e9fa92 --- /dev/null +++ b/packages/component-manuscript/src/inviteReviewer/withInviteReviewer.md @@ -0,0 +1,76 @@ +## Hindawi Reviewer Invite HOC. + +Injects `onInviteReviewer`, `onInvitePublonReviewer`, `onResendInviteReviewer`, `onRevokeInviteReviewer` and `onReviewerResponse` handlers as props. + +### withInviteReviewer props + +* `inviteReviewer: object`: namespace containing the following fields: + * `onInviteReviewer: function`: sends an invitation to the reviewer. + * `onInvitePublonReviewer: function`: sends an invitation to a Publon reviewer. + * `onResendInviteReviewer: function`: resends an invitation to an already invited reviewer. + * `onRevokeInviteReviewer: function`: cancels an invitation to an invited reviewer. + * `onReviewerResponse: function`: handles the reviewer response to the invitation. + +```javascript +const InviteReviewer = ({ + onInviteReviewer, + onInvitePublonReviewer, + onResendInviteReviewer, + onRevokeInviteReviewer, +}) => ( + <Modal> + <span>Reviewers list</span> + <div> + <span>Revi Ewerin</span> + <button + onClick={() => onInviteReviewer(values, { ...modalProps, setFetching })} + > + Invite + </button> + </div> + <div> + <span>Publonus re' Vyewer</span> + <button + onClick={() => + onInvitePublonReviewer(reviewerData, { ...modalProps, setFetching }) + } + > + Invite + </button> + </div> + <div> + <span>Rev d'Iewer</span> + <button + onClick={() => + onResendInviteReviewer(email, { ...modalProps, setFetching }) + } + > + Resend invitation + </button> + <button + onClick={() => + onRevokeInviteReviewer(invitationId, { ...modalProps, setFetching }) + } + > + Cancel invitation + </button> + </div> + </Modal> +) + +const Invitation = ({ onReviewerResponse }) => ( + <Modal> + <span>Accept invitation?</span> + <button + onClick={() => onReviewerResponse(values, { ...modalProps, setFetching })} + > + Yes + </button> + <button + onClick={() => onReviewerResponse(values, { ...modalProps, setFetching })} + > + No + </button> + </Modal> +) +```