diff --git a/.eslintrc.js b/.eslintrc.js index 4bb224699202e35495b8e20dca596441531ac692..c7f8ffb61c492df34fe35395e1571ab3803864c9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,7 +22,7 @@ eslint.rules = { 'import/no-extraneous-dependencies': [ 'error', { - devDependencies: ['.storybook/**', 'stories/**'], + devDependencies: ['.storybook/**', 'stories/**', '.cz-config.js'], }, ], 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }], diff --git a/stories/_helpers.js b/stories/_helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..8e531815c72789284364a4a031e7d40c184a0a3d --- /dev/null +++ b/stories/_helpers.js @@ -0,0 +1,40 @@ +/* eslint-disable react/prop-types */ + +import React from 'react'; +import styled from 'styled-components'; + +const DemoWrapper = styled.div` + margin-bottom: 24px; + border-bottom: 1px solid gray; + padding: 12px; + display: flex; + justify-content: flex-end; + + > span { + background: gray; + color: white; + margin-left: 8px; + padding: 4px 8px; + } +`; + +const Demo = props => { + const { buttonText, children, onClickButton } = props; + const noop = () => {}; + + return ( + <> + <DemoWrapper> + <button onClick={onClickButton || noop} type="button"> + {buttonText || 'Reset state'} + </button> + <span>demo purposes only</span> + </DemoWrapper> + + {children} + </> + ); +}; + +/* eslint-disable import/prefer-default-export */ +export { Demo }; diff --git a/stories/comments/CommentBox.stories.js b/stories/comments/CommentBox.stories.js index 7d3fc9ce318a1d13eb6bc2c4e540947e240256f1..5193ceb61560ae6f6879ba7c4980e367fe9f9c3e 100644 --- a/stories/comments/CommentBox.stories.js +++ b/stories/comments/CommentBox.stories.js @@ -1,43 +1,21 @@ -import React, { useState } from 'react' -import { lorem, name } from 'faker' -import { range } from 'lodash' -import styled from 'styled-components' +import React, { useState } from 'react'; +import { lorem, name } from 'faker'; +import { range } from 'lodash'; -import CommentBox from '../../wax-prosemirror-components/src/ui/comments/CommentBox' +import CommentBox from '../../wax-prosemirror-components/src/ui/comments/CommentBox'; +import { Demo } from '../_helpers'; const commentData = range(5).map(() => ({ content: lorem.sentences(3), displayName: name.findName(), timestamp: '3 days ago', -})) - -const Demo = styled.div` - margin-bottom: 24px; - border-bottom: 1px solid gray; - padding: 12px; - display: flex; - justify-content: flex-end; - - > span { - background: gray; - color: white; - margin-left: 8px; - padding: 4px 8px; - } -` +})); export const Base = () => { - const [active, setActive] = useState(false) + const [active, setActive] = useState(false); return ( - <> - <Demo> - <button onClick={() => setActive(false)} type="button"> - reset state - </button> - <span>demo purposes only</span> - </Demo> - + <Demo onClickButton={() => setActive(false)}> <CommentBox active={active} commentData={commentData} @@ -45,9 +23,9 @@ export const Base = () => { onClickBox={id => setActive(true)} onClickResolve={id => console.log('resolve id', id)} /> - </> - ) -} + </Demo> + ); +}; export const NotActive = () => ( <CommentBox @@ -56,7 +34,7 @@ export const NotActive = () => ( onClickBox={id => console.log('set active', id)} onClickResolve={id => console.log('resolve id', id)} /> -) +); export const Active = () => ( <CommentBox @@ -66,9 +44,9 @@ export const Active = () => ( onClickBox={id => console.log('set active', id)} onClickResolve={id => console.log('resolve id', id)} /> -) +); export default { component: CommentBox, title: 'Comments/Comment Box', -} +}; diff --git a/stories/trackChanges/TrackChangesBox.stories.js b/stories/trackChanges/TrackChangesBox.stories.js new file mode 100644 index 0000000000000000000000000000000000000000..04c612fa58ac8046143f6805e634d0099b9f4b1a --- /dev/null +++ b/stories/trackChanges/TrackChangesBox.stories.js @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { lorem, name } from 'faker'; + +import TrackChangesBox from '../../wax-prosemirror-components/src/ui/trackChanges/TrackChangesBox'; +import { Demo } from '../_helpers'; + +export const Base = () => { + const [active, setActive] = useState(false); + const makeActive = () => setActive(!active); + + return ( + <Demo onClickButton={() => setActive(false)}> + <TrackChangesBox + active={active} + displayName={name.findName()} + label="add" + onClick={makeActive} + onClickAccept={() => console.log('accept!')} + onClickReject={() => console.log('reject!')} + text={lorem.words(7)} + timestamp="2 days ago" + /> + </Demo> + ); +}; + +export const NotActive = () => ( + <TrackChangesBox label="add" text={lorem.words(7)} /> +); + +export const Active = () => ( + <TrackChangesBox + active + displayName={name.findName()} + label="add" + onClickAccept={() => console.log('accept!')} + onClickReject={() => console.log('reject!')} + text={lorem.words(7)} + timestamp="2 days ago" + /> +); + +export default { + component: TrackChangesBox, + title: 'Track Changes/Track Changes Box', +}; diff --git a/wax-prosemirror-components/src/icons/icons.js b/wax-prosemirror-components/src/icons/icons.js index d7d01c5462961b2842d5e30f1f08437e95809729..c83940873b03673b5e33e98343a3ed3b01544624 100644 --- a/wax-prosemirror-components/src/icons/icons.js +++ b/wax-prosemirror-components/src/icons/icons.js @@ -3,6 +3,7 @@ import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import { faBold, faItalic, + faCheck, faCode, faSuperscript, faSubscript, @@ -16,6 +17,7 @@ import { faListUl, faImage, faTable, + faTimes, faUndo, faRedo, faOutdent, @@ -71,4 +73,6 @@ export default { </svg> </span> ), + check: <FontAwesomeIcon icon={faCheck} />, + times: <FontAwesomeIcon icon={faTimes} />, }; diff --git a/wax-prosemirror-components/src/ui/trackChanges/TrackChangesBox.js b/wax-prosemirror-components/src/ui/trackChanges/TrackChangesBox.js new file mode 100644 index 0000000000000000000000000000000000000000..99083cb8680b39561fd3517bc303d57d0d341414 --- /dev/null +++ b/wax-prosemirror-components/src/ui/trackChanges/TrackChangesBox.js @@ -0,0 +1,139 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled, { css } from 'styled-components'; +import icons from '../../icons/icons'; + +const { check, times } = icons; + +const activeBorder = css` + border-color: gray; +`; + +const Wrapper = styled.div` + border: 2px solid transparent; + border-radius: 5px; + cursor: pointer; + padding: 8px 16px; + transition: border 0.1s ease-in; + + ${props => props.active && activeBorder} + + &:hover { + ${activeBorder} + } +`; + +const HeadWrapper = styled.div` + display: flex; + margin-bottom: 8px; +`; + +const Info = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; +`; + +const Name = styled.div``; + +const Timestamp = styled.div` + font-color: gray; +`; + +const Tools = styled.div``; + +const ChangeWrapper = styled.div``; + +const Label = styled.span` + font-weight: bold; + margin-right: 4px; + text-transform: capitalize; + + &:after { + content: ':'; + } +`; + +const Text = styled.span``; + +const Icon = styled.div` + border-radius: 3px; + display: inline-block; + padding: 4px; + width: 16px; + height: 16px; + transition: background 0.1s ease-in; + + &:hover { + background: gray; + } +`; + +const IconButton = props => { + // eslint-disable-next-line react/prop-types + const { icon, onClick } = props; + + const handleClick = e => { + e.stopPropagation(); + onClick(); + }; + + return ( + <Icon onClick={handleClick} type="button"> + {icon} + </Icon> + ); +}; + +const TrackChangesBox = props => { + const { + active, + className, + displayName, + label, + onClick, + onClickAccept, + onClickReject, + text, + timestamp, + } = props; + + return ( + <Wrapper active={active} onClick={onClick} className={className}> + {active && ( + <HeadWrapper> + <Info> + <Name>{displayName}</Name> + <Timestamp>{timestamp}</Timestamp> + </Info> + + <Tools> + <IconButton icon={check} onClick={onClickAccept} /> + <IconButton icon={times} onClick={onClickReject} /> + </Tools> + </HeadWrapper> + )} + + <ChangeWrapper> + <Label>{label}</Label> + <Text>{text}</Text> + </ChangeWrapper> + </Wrapper> + ); +}; + +TrackChangesBox.propTypes = { + active: PropTypes.bool, + displayName: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + onClickAccept: PropTypes.func.isRequired, + onClickReject: PropTypes.func.isRequired, + text: PropTypes.string.isRequired, + timestamp: PropTypes.string.isRequired, +}; + +TrackChangesBox.defaultProps = { + active: false, +}; + +export default TrackChangesBox;