diff --git a/package-lock.json b/package-lock.json index 76c00a1..1a383b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2201,6 +2201,11 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "are-passive-events-supported": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/are-passive-events-supported/-/are-passive-events-supported-1.1.1.tgz", + "integrity": "sha512-5wnvlvB/dTbfrCvJ027Y4L4gW/6Mwoy1uFSavney0YO++GU+0e/flnjiBBwH+1kh7xNCgCOGvmJC3s32joYbww==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -8427,8 +8432,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -8446,13 +8450,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8465,18 +8467,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -8579,8 +8578,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -8590,7 +8588,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8603,20 +8600,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8633,7 +8627,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8706,8 +8699,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -8717,7 +8709,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8793,8 +8784,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -8824,7 +8814,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8842,7 +8831,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8881,13 +8869,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -20722,6 +20708,20 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-latest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.0.0.tgz", + "integrity": "sha512-CxmFi75KTXeTIBlZq3LhJ4Hz98pCaRKZHCpnbiaEHIr5QnuHvH8lKYoluPBt/ik7j/hFVPB8K3WqF6mQvLyQTg==" + }, + "use-onclickoutside": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/use-onclickoutside/-/use-onclickoutside-0.3.1.tgz", + "integrity": "sha512-aahvbW5+G0XJfzj31FJeLsvc6qdKbzeTsQ8EtkHHq5qTg6bm/qkJeKLcgrpnYeHDDbd7uyhImLGdkbM9BRzOHQ==", + "requires": { + "are-passive-events-supported": "^1.1.0", + "use-latest": "^1.0.0" + } + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", diff --git a/package.json b/package.json index 45b26c8..e5ae7a2 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "terser-webpack-plugin": "1.2.2", "typescript": "^3.3.3333", "url-loader": "1.1.2", + "use-onclickoutside": "^0.3.1", "webpack": "4.28.3", "webpack-dev-server": "3.1.14", "webpack-manifest-plugin": "2.0.4", diff --git a/src/features/cards/organisms/card-item.js b/src/features/cards/organisms/card-item.js index c22fa77..deecf3c 100644 --- a/src/features/cards/organisms/card-item.js +++ b/src/features/cards/organisms/card-item.js @@ -1,18 +1,72 @@ -import React from "react" +import React, { useState } from "react" import styled from "styled-components" import PropTypes from "prop-types" import { format } from "date-fns" import { RichEditor } from "@lib/rich-text" -import { Col, Row } from "@lib/styled-components-layout" -import { Card, H3, Link, Button, ButtonPrimary } from "@howtocards/ui" +import { Row } from "@lib/styled-components-layout" +import { Link, H2, Icon, Text, Modal } from "@howtocards/ui" + +const CardBox = styled.div` + margin: 0.5rem; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + padding: 2rem; + max-height: 24rem; + box-sizing: border-box; + overflow-y: hidden; + + &: hover { + display: flex; + flex-flow: column; + flex-shrink: 0; + border-radius: 4px; + padding: 2rem; + box-sizing: border-box; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2); + //transform: scale(1.001); + } +` + +const GridCard = styled.div` + display: grid; + grid-template-areas: + "flag header" + "flag info" + "flag content" + "flag footer"; + grid-template-rows: 2rem 2rem 10rem 3rem; + grid-template-columns: 50px 1fr; + grid-gap: 8px; +` export const CardItem = ({ onUsefulClick, card }) => ( - - - - + + + + + + + + + + + + + + + + + + + 14 Comments + + ) @@ -29,29 +83,107 @@ CardItem.propTypes = { onUsefulClick: PropTypes.func.isRequired, } -const CardHeading = ({ card, onUsefulClick }) => ( +const CardFlagWithNumber = ({ usefulFor, isUseful, onUsefulClick }) => ( +
+ + + {usefulFor} + +
+) + +CardFlagWithNumber.propTypes = { + onUsefulClick: PropTypes.func.isRequired, + usefulFor: PropTypes.number.isRequired, + isUseful: PropTypes.bool.isRequired, +} + +const FavouriteButton = ({ isUseful, onUsefulClick }) => { + return ( + + {isUseful ? ( + + ) : ( + + )} + + ) +} + +FavouriteButton.propTypes = { + onUsefulClick: PropTypes.func.isRequired, + isUseful: PropTypes.bool.isRequired, +} + +const CardHeader = ({ card }) => ( -

{card.title}

+

{card.title}

- - {card.meta.isUseful ? ( - - Saved - - ) : ( - - )} - {card.usefulFor > 0 ?
{card.usefulFor}
:
 
} + {card.meta.canEdit && Edit} - {format(new Date(card.createdAt), "HH:MM MM/DD/YYYY")} + +
) +const CardInfo = (card) => ( + + + {format(new Date(card.createdAt), "HH:MM MM/DD/YYYY")} by Author + + +) + +const HeadingLine = styled.div` + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + flex-shrink: 0; + line-height: 2.4rem; +` +const IconWrapper = styled.div` + padding: 0 0.6rem; -CardHeading.propTypes = { + &:disabled { + opacity: 0.3; + } + + & #icon:hover { + fill: green; + cursor: pointer; + transition: fill 0.7s; + } +` + +const CenterHorizontal = styled.div` + display: flex; + justify-content: center; + flex-grow: 1; + width: 100%; +` +const CellCardFooter = styled.div` + grid-area: footer; + align-items: flex-end; + line-height: 2.4rem; + padding: 8px 0; +` + +const CellCardFlag = styled.div` + grid-area: flag; +` +const CellCardHeader = styled.div` + grid-area: header; +` +const CellCardInfo = styled.div` + grid-area: info; +` +const CellCardContent = styled.div` + grid-area: content; + overflow-y: hidden; +` + +CardHeader.propTypes = { card: PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, @@ -61,18 +193,40 @@ CardHeading.propTypes = { isUseful: PropTypes.bool, }).isRequired, }).isRequired, - onUsefulClick: PropTypes.func.isRequired, } -const HeadingLine = styled.div` - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - flex-shrink: 0; - line-height: 2.4rem; -` +const CardDeleteModalButton = ({ card }) => { + const [opened, setOpened] = useState(false) + const close = () => setOpened(() => false) + const toggle = () => setOpened((isOpen) => !isOpen) -const CardBox = styled(Card)` - max-height: 24rem; - overflow-y: hidden; + return ( +
+ + + + + {opened && ( + + Do you absolutely sure you want to delete article about + + “ + {card.title} ” + + ? Just kidding we are archiving them anyway. + + )} +
+ ) +} + +CardDeleteModalButton.propTypes = { + card: PropTypes.shape({ + title: PropTypes.string.isRequired, + }).isRequired, +} + +export const ZeroButton = styled.button` + background-color: transparent; + border: none; ` diff --git a/src/ui/atoms/box.js b/src/ui/atoms/box.js new file mode 100644 index 0000000..1396258 --- /dev/null +++ b/src/ui/atoms/box.js @@ -0,0 +1,45 @@ +import styled, { css } from "styled-components" + +export const Box = styled.div` + margin: 0.5rem; + display: flex; + flex-flow: column; + flex-shrink: 0; + ${({ theme }) => theme.embed.card} + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + padding: 2rem; + box-sizing: border-box; + &: hover { + display: flex; + flex-flow: column; + flex-shrink: 0; + border-radius: 4px; + padding: 2rem; + box-sizing: border-box; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2); + //transform: scale(1.001); + } + ${({ sticky }) => + sticky && + css` + position: sticky; + top: 2rem; + &: hover { + display: flex; + flex-flow: column; + flex-shrink: 0; + border-radius: 4px; + padding: 2rem; + box-sizing: border-box; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2); + } + `} + ${({ popup }) => + popup && + css` + width: 450px; + position: fixed; + z-index: 9; + `} +` diff --git a/src/ui/atoms/button.js b/src/ui/atoms/button.js index 3b3f42a..1d78ac3 100644 --- a/src/ui/atoms/button.js +++ b/src/ui/atoms/button.js @@ -10,6 +10,11 @@ export const Button = styled.button` transition: box-shadow 120ms; user-select: none; + &:hover { + background: #3a7bd5; + color: white; + } + ${({ small }) => small && css` @@ -17,6 +22,7 @@ export const Button = styled.button` font-size: 1rem; `} + ${({ theme }) => theme.embed.button.primary} ${({ grow }) => grow && diff --git a/src/ui/atoms/icon.js b/src/ui/atoms/icon.js new file mode 100644 index 0000000..8aa34d2 --- /dev/null +++ b/src/ui/atoms/icon.js @@ -0,0 +1,80 @@ +import React from "react" +import PropTypes from "prop-types" +// When you need more icons just add svg-path in getIconPath and call it +// with + +const getIconPath = (name, props) => { + switch (name) { + case "bookmark-regular": + return ( + + ) + case "bookmark-solid": + return ( + + ) + case "x": + return ( + + ) + case "dots-v": + return ( + + ) + case "trash": + return ( + + ) + default: + return + } +} + +export const Icon = ({ + id = "icon", + name = "", + width = "2.4em", + height = "2.4em", + fill = "#000", + style = {}, + viewBox = "0 0 384 512", +}) => ( + +) + +Icon.propTypes = { + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + width: PropTypes.string.isRequired, + height: PropTypes.string.isRequired, + fill: PropTypes.string.isRequired, + style: PropTypes.string.isRequired, + viewBox: PropTypes.string.isRequired, +} diff --git a/src/ui/atoms/index.js b/src/ui/atoms/index.js index 89a4c3e..f5aab42 100644 --- a/src/ui/atoms/index.js +++ b/src/ui/atoms/index.js @@ -6,3 +6,5 @@ export { H1, H2, H3 } from "./heading" export { Input } from "./input" export { Link } from "./link" export { Text } from "./text" +export { Icon } from "./icon" +export { Box } from "./box" diff --git a/src/ui/organisms/index.js b/src/ui/organisms/index.js index decfb10..2d6d085 100644 --- a/src/ui/organisms/index.js +++ b/src/ui/organisms/index.js @@ -1,3 +1,4 @@ export { PrimitiveFooter } from "./primitive-footer" export { ItemsList } from "./items-list" export { ConditionalList } from "./conditional-list" +export { Modal } from "./modal" diff --git a/src/ui/organisms/modal.js b/src/ui/organisms/modal.js new file mode 100644 index 0000000..84de270 --- /dev/null +++ b/src/ui/organisms/modal.js @@ -0,0 +1,86 @@ +import React, { useRef } from "react" +import styled from "styled-components" +import useOnClickOutside from "use-onclickoutside" +import PropTypes from "prop-types" + +import { H3, Icon, Text, Button, Box } from "@howtocards/ui" +import { Row } from "@lib/styled-components-layout" + +export const Modal = ({ children, close, onDeleteClick }) => { + const ref = useRef(null) + useOnClickOutside(ref, close) + + return ( +
+ + + +

Do you want to delete?

+
+ + + + + + + + + {children} + + + + + + + + +
+
+
+ ) +} + +Modal.propTypes = { + children: PropTypes.node.isRequired, + close: PropTypes.func.isRequired, + onDeleteClick: PropTypes.func, +} + +Modal.defaultProps = { + onDeleteClick: () => {}, +} + +const GridPopUp = styled.div` + display: grid; + grid-template-areas: + "popupHeading popupClose" + "popupContent popupContent" + "popupButtonYes popupButtonNo"; + grid-template-rows: 5rem 1fr 3rem; + grid-template-columns: 50% 50%; + grid-gap: 8px; +` +const CellPopUpHeading = styled.div` + grid-area: popupHeading; +` +const CellPopUpClose = styled.div` + grid-area: popupClose; +` +const CellPopUpContent = styled.div` + grid-area: popupContent; +` +const CellPopUpButtonYes = styled.div` + grid-area: popupButtonYes; +` +const CellPopUpButtonNo = styled.div` + grid-area: popupButtonNo; +`