From c541278686311c29aee1dbf81ffc5b23f1a95661 Mon Sep 17 00:00:00 2001 From: muerwre Date: Mon, 26 Aug 2019 13:17:55 +0700 Subject: [PATCH] comment form --- .eslintrc.js | 3 +- .../containers/CommentWrapper/index.tsx | 36 +++++ .../containers/CommentWrapper/styles.scss | 26 ++++ src/components/input/Button/index.tsx | 53 ++++---- src/components/input/Button/styles.scss | 17 ++- src/components/input/Textarea/index.tsx | 124 ++++++++++++++++++ src/components/node/Comment/index.tsx | 30 ++--- src/components/node/Comment/styles.scss | 27 ---- src/components/node/CommentForm/index.tsx | 49 +++++++ src/components/node/CommentForm/styles.scss | 19 +++ src/components/node/NodeComments/index.tsx | 6 +- src/containers/node/NodeLayout/index.tsx | 3 + src/redux/types.ts | 8 ++ src/sprites/Sprites.tsx | 10 ++ src/styles/inputs.scss | 35 +++-- 15 files changed, 357 insertions(+), 89 deletions(-) create mode 100644 src/components/containers/CommentWrapper/index.tsx create mode 100644 src/components/containers/CommentWrapper/styles.scss create mode 100644 src/components/input/Textarea/index.tsx create mode 100644 src/components/node/CommentForm/index.tsx create mode 100644 src/components/node/CommentForm/styles.scss diff --git a/.eslintrc.js b/.eslintrc.js index 944a5dd1..9a6abfaa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -53,7 +53,8 @@ module.exports = { "functions": "never" }], indent: "off", - "import/order": "off" + "import/order": "off", + "arrow-parens": ["warn", "as-needed"], }, globals: { document: false, diff --git a/src/components/containers/CommentWrapper/index.tsx b/src/components/containers/CommentWrapper/index.tsx new file mode 100644 index 00000000..f50b41d8 --- /dev/null +++ b/src/components/containers/CommentWrapper/index.tsx @@ -0,0 +1,36 @@ +import React, { FC, HTMLAttributes } from 'react'; +import classNames from 'classnames'; + +import * as styles from './styles.scss'; +import { Card } from '../Card'; + +type IProps = HTMLAttributes & { + photo?: string; + is_empty?: boolean; + is_loading?: boolean; +}; + +const CommentWrapper: FC = ({ + photo, + children, + is_empty, + is_loading, + className, + ...props +}) => ( + +
+ {photo && ( +
+ )} +
+ +
{children}
+ +); + +export { CommentWrapper }; diff --git a/src/components/containers/CommentWrapper/styles.scss b/src/components/containers/CommentWrapper/styles.scss new file mode 100644 index 00000000..c5c820d9 --- /dev/null +++ b/src/components/containers/CommentWrapper/styles.scss @@ -0,0 +1,26 @@ +.wrap { + background: $comment_bg; + min-height: 64px; + display: flex; + box-shadow: $comment_shadow; + + &:global(.is_empty) { + opacity: 0.5; + } +} + +.text { + flex: 1; +} + +.thumb { + flex: 0 0 64px; + background: transparentize(black, 0.9); + border-radius: $panel_radius 0 0 $panel_radius; +} + +.thumb_image { + height: 64px; + background: transparentize(white, 0.97); + border-radius: $panel_radius 0 0 $panel_radius; +} diff --git a/src/components/input/Button/index.tsx b/src/components/input/Button/index.tsx index 8f3c748b..a7f3c6b5 100644 --- a/src/components/input/Button/index.tsx +++ b/src/components/input/Button/index.tsx @@ -1,12 +1,12 @@ import classnames from 'classnames'; -import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC } from 'react'; +import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC, createElement } from 'react'; import * as styles from './styles.scss'; import { Icon } from '~/components/input/Icon'; import { IIcon } from '~/redux/types'; type IButtonProps = DetailedHTMLProps< -ButtonHTMLAttributes, -HTMLButtonElement + ButtonHTMLAttributes, + HTMLButtonElement > & { size?: 'mini' | 'normal' | 'big' | 'giant' | 'micro'; iconLeft?: IIcon; @@ -35,26 +35,29 @@ export const Button: FC = ({ is_loading, title, stretchy, + disabled, ...props -}) => React.createElement(seamless || non_submitting ? 'div' : 'button', { - className: classnames(styles.button, className, styles[size], { - red, - grey, - seamless, - transparent, - disabled: props.disabled, - icon: (iconLeft || iconRight) && !title && !children, - is_loading, - stretchy - }), - children: [ - iconLeft && , - title ? ( - {title} - ) : ( - (children && {children}) || null - ), - iconRight && - ], - ...props -}); +}) => + createElement( + seamless || non_submitting ? 'div' : 'button', + { + className: classnames(styles.button, className, styles[size], { + red, + grey, + seamless, + transparent, + disabled, + is_loading, + stretchy, + icon: (iconLeft || iconRight) && !title && !children, + has_icon_left: !!iconLeft, + has_icon_right: !!iconRight, + }), + ...props, + }, + [ + iconLeft && , + title ? {title} : (children && {children}) || null, + iconRight && , + ] + ); diff --git a/src/components/input/Button/styles.scss b/src/components/input/Button/styles.scss index 3d107e87..7a850656 100644 --- a/src/components/input/Button/styles.scss +++ b/src/components/input/Button/styles.scss @@ -85,8 +85,8 @@ &:global(.disabled), &:global(.grey) { opacity: 0.3; - //background: black; - filter: grayscale(100%); + background: lighten(black, 2%); + // filter: grayscale(100%); } &:global(.icon) { @@ -101,6 +101,16 @@ } } + &:global(.has_icon_left) { + padding-left: $gap; + padding-right: $gap; + } + + &:global(.has_icon_right) { + padding-left: $gap; + padding-right: $gap; + } + > * { margin: 0 5px; @@ -117,9 +127,11 @@ height: 20px; font: $font_12_semibold; padding: 0 15px; + border-radius: $radius / 2; } .mini { height: 28px; + border-radius: $radius / 2; } .normal { height: 38px; @@ -135,6 +147,7 @@ .disabled { opacity: 0.5; } + .icon_left { margin-right: 10px; width: 20px; diff --git a/src/components/input/Textarea/index.tsx b/src/components/input/Textarea/index.tsx new file mode 100644 index 00000000..92d2aac3 --- /dev/null +++ b/src/components/input/Textarea/index.tsx @@ -0,0 +1,124 @@ +import React, { + ChangeEvent, + LegacyRef, + memo, + useCallback, + useLayoutEffect, + useRef, + useState, +} from 'react'; +import { getStyle } from '~/utils/dom'; +import classNames from 'classnames'; + +import * as styles from '~/styles/inputs.scss'; +import { Icon } from '../Icon'; + +interface IProps { + value: string; + placeholder?: string; + rows?: number; + className?: string; + minRows?: number; + maxRows?: number; + handler: (value: string) => void; + required?: boolean; + status?: 'error' | 'success' | ''; + title?: string; +} + +const Textarea = memo( + ({ + value, + placeholder, + className, + minRows = 3, + maxRows = 30, + handler, + required = false, + title = '', + status = '', + }) => { + const [rows, setRows] = useState(minRows || 1); + const [focused, setFocused] = useState(false); + + const textarea: LegacyRef = useRef(null); + + const onInput = useCallback( + ({ target }: ChangeEvent) => handler(target.value), + [handler] + ); + + const onFocus = useCallback(() => setFocused(true), [setFocused]); + const onBlur = useCallback(() => setFocused(false), [setFocused]); + + useLayoutEffect(() => { + const lineHeight = parseInt(getStyle(textarea.current, 'line-height'), 10) || 15; + + textarea.current.rows = 1; // reset number of rows in textarea + + const paddingTop = parseInt(getStyle(textarea.current, 'padding-top'), 10) || 0; + const paddingBottom = parseInt(getStyle(textarea.current, 'padding-bottom'), 10) || 0; + + const actualScrollHeight = + (textarea.current.scrollHeight || 0) - (paddingTop + paddingBottom); + + const rowsCount = Math.round(actualScrollHeight / lineHeight); + + let currentRows = minRows; + + if (rowsCount > maxRows) { + currentRows = maxRows; + textarea.current.scrollTop = textarea.current.scrollHeight; + } else if (rowsCount <= minRows) { + currentRows = minRows; + } else { + currentRows = rowsCount; + } + + textarea.current.rows = currentRows; + + setRows(currentRows); + }, [value, minRows, maxRows]); + + return ( +
+
+