From e2c1cba9549c4bd2ada5ac7babe5ccc9c3a35ea7 Mon Sep 17 00:00:00 2001
From: Yannis Barlas <yannisbarlas@gmail.com>
Date: Mon, 7 Sep 2020 20:05:38 +0300
Subject: [PATCH] feat(ui): add track changes box

---
 .eslintrc.js                                  |   2 +-
 stories/_helpers.js                           |  40 +++++
 stories/comments/CommentBox.stories.js        |  50 ++-----
 .../trackChanges/TrackChangesBox.stories.js   |  46 ++++++
 wax-prosemirror-components/src/icons/icons.js |   4 +
 .../src/ui/trackChanges/TrackChangesBox.js    | 139 ++++++++++++++++++
 6 files changed, 244 insertions(+), 37 deletions(-)
 create mode 100644 stories/_helpers.js
 create mode 100644 stories/trackChanges/TrackChangesBox.stories.js
 create mode 100644 wax-prosemirror-components/src/ui/trackChanges/TrackChangesBox.js

diff --git a/.eslintrc.js b/.eslintrc.js
index 4bb224699..c7f8ffb61 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 000000000..8e531815c
--- /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 7d3fc9ce3..5193ceb61 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 000000000..04c612fa5
--- /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 d7d01c546..c83940873 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 000000000..99083cb86
--- /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;
-- 
GitLab