From 336582b3d6da40726ba3e0ab0ab897251c310a2e Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Sun, 13 Oct 2019 21:11:20 +0700 Subject: [PATCH] floating node panel --- src/components/node/NodeImageBlock/index.tsx | 6 +- src/components/node/NodePanel/index.tsx | 55 +++++---- src/components/node/NodePanel/styles.scss | 91 +-------------- src/components/node/NodePanelInner/index.tsx | 39 +++++++ .../node/NodePanelInner/styles.scss | 107 ++++++++++++++++++ src/containers/node/NodeLayout/index.tsx | 10 +- src/redux/node/constants.ts | 5 +- 7 files changed, 200 insertions(+), 113 deletions(-) create mode 100644 src/components/node/NodePanelInner/index.tsx create mode 100644 src/components/node/NodePanelInner/styles.scss diff --git a/src/components/node/NodeImageBlock/index.tsx b/src/components/node/NodeImageBlock/index.tsx index a8b44c53..b6d69ded 100644 --- a/src/components/node/NodeImageBlock/index.tsx +++ b/src/components/node/NodeImageBlock/index.tsx @@ -18,9 +18,11 @@ import { UPLOAD_TYPES } from '~/redux/uploads/constants'; interface IProps { is_loading: boolean; node: INode; + layout: {}; + updateLayout: () => void; } -const NodeImageBlock: FC = ({ node, is_loading }) => { +const NodeImageBlock: FC = ({ node, is_loading, updateLayout }) => { const [is_animated, setIsAnimated] = useState(false); const [current, setCurrent] = useState(0); const [height, setHeight] = useState(320); @@ -39,6 +41,8 @@ const NodeImageBlock: FC = ({ node, is_loading }) => { loaded, ]); + useEffect(() => updateLayout(), [loaded]); + useEffect(() => { if (!refs || !refs.current[current] || !loaded[current]) return setHeight(320); diff --git a/src/components/node/NodePanel/index.tsx b/src/components/node/NodePanel/index.tsx index cdec1c87..9d93e8a9 100644 --- a/src/components/node/NodePanel/index.tsx +++ b/src/components/node/NodePanel/index.tsx @@ -1,33 +1,48 @@ -import React, { FC } from 'react'; +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import * as styles from './styles.scss'; -import { Group } from '~/components/containers/Group'; -import { Filler } from '~/components/containers/Filler'; -import { Icon } from '~/components/input/Icon'; import { INode } from '~/redux/types'; +import { createPortal } from 'react-dom'; +import { NodePanelInner } from '~/components/node/NodePanelInner'; interface IProps { node: INode; + layout: {}; } -const NodePanel: FC = ({ node: { title, user } }) => ( -
- - -
{title || '...'}
- {user && user.username &&
~ {user.username}
} -
-
+const NodePanel: FC = ({ node, layout }) => { + const [stack, setStack] = useState(false); -
- + const ref = useRef(null); + const getPlace = useCallback(() => { + if (!ref.current) return; -
+ const { offsetTop } = ref.current; + const { height } = ref.current.getBoundingClientRect(); + const { scrollY, innerHeight } = window; - + setStack(offsetTop > scrollY + innerHeight - height); + }, [ref]); + + useEffect(() => { + getPlace(); + window.addEventListener('scroll', getPlace); + window.addEventListener('resize', getPlace); + + return () => { + window.removeEventListener('scroll', getPlace); + window.removeEventListener('resize', getPlace); + }; + }, [layout]); + + return ( +
+ {stack ? ( + createPortal(, document.body) + ) : ( + + )}
-
-); + ); +}; export { NodePanel }; - -//
diff --git a/src/components/node/NodePanel/styles.scss b/src/components/node/NodePanel/styles.scss index 6f346c11..009c0832 100644 --- a/src/components/node/NodePanel/styles.scss +++ b/src/components/node/NodePanel/styles.scss @@ -1,91 +1,6 @@ -.wrap { - background: $node_bg; - padding: $gap; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: stretch; - border-radius: $radius $radius 0 0; +.place { + height: 72px; position: relative; - margin-top: -$radius; z-index: 3; -} - -.title { - text-transform: uppercase; - font: $font_24_semibold; - height: 24px; - padding-bottom: 6px; -} - -.name { - font: $font_14_regular; - color: transparentize(white, 0.5); -} - -.btn { - flex: 1; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - fill: transparentize(white, 0.5); -} - -.panel { - flex: 1; -} - -.buttons { - flex: 0; - padding-right: $gap; - fill: transparentize(white, 0.7); - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - - & > * { - margin: 0 $gap; - - &:first-child { - margin-left: 0; - } - &:last-child { - margin-right: 0; - } - } - //height: 54px; - //border-radius: $radius $radius 0 0; - //background: linear-gradient(176deg, #f42a00, #5c1085); - //position: absolute; - //bottom: 0; - //right: 10px; - //width: 270px; - //display: flex; -} - -.mark { - flex: 0 0 32px; - position: relative; - - &::after { - content: ' '; - position: absolute; - top: -38px; - right: 4px; - width: 24px; - height: 52px; - background: $green_gradient; - box-shadow: transparentize(black, 0.8) 4px 2px; - border-radius: 2px; - } -} - -.sep { - flex: 0 0 6px; - height: 6px; - width: 6px; - border-radius: 4px; - background: transparentize(black, 0.7); + margin-top: -$radius; } diff --git a/src/components/node/NodePanelInner/index.tsx b/src/components/node/NodePanelInner/index.tsx new file mode 100644 index 00000000..49d56bcd --- /dev/null +++ b/src/components/node/NodePanelInner/index.tsx @@ -0,0 +1,39 @@ +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import * as styles from './styles.scss'; +import { Group } from '~/components/containers/Group'; +import { Filler } from '~/components/containers/Filler'; +import { Icon } from '~/components/input/Icon'; +import { INode } from '~/redux/types'; +import classNames from 'classnames'; + +interface IProps { + node: INode; + stack?: boolean; +} + +const NodePanelInner: FC = ({ node: { title, user }, stack }) => { + return ( +
+
+ + +
{title || '...'}
+ {user && user.username &&
~ {user.username}
} +
+
+ +
+ + +
+ + +
+
+
+ ); +}; + +export { NodePanelInner }; + +//
diff --git a/src/components/node/NodePanelInner/styles.scss b/src/components/node/NodePanelInner/styles.scss new file mode 100644 index 00000000..c0ba5407 --- /dev/null +++ b/src/components/node/NodePanelInner/styles.scss @@ -0,0 +1,107 @@ +.wrap { + display: flex; + align-items: center; + justify-content: stretch; + position: relative; + height: 72px; + width: 100%; + flex-direction: row; + align-items: center; + justify-content: center; + + &:global(.stack) { + bottom: 0; + position: fixed; + } +} + +.content { + flex: 0 1 $content_width; + display: flex; + align-items: center; + justify-content: stretch; + border-radius: $radius $radius 0 0; + box-sizing: border-box; + padding: $gap; + background: $node_bg; + height: 72px; +} + +.title { + text-transform: uppercase; + font: $font_24_semibold; + height: 24px; + padding-bottom: 6px; +} + +.name { + font: $font_14_regular; + color: transparentize(white, 0.5); +} + +.btn { + flex: 1; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + fill: transparentize(white, 0.5); +} + +.panel { + flex: 1; +} + +.buttons { + flex: 0; + padding-right: $gap; + fill: transparentize(white, 0.7); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + & > * { + margin: 0 $gap; + + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + } + //height: 54px; + //border-radius: $radius $radius 0 0; + //background: linear-gradient(176deg, #f42a00, #5c1085); + //position: absolute; + //bottom: 0; + //right: 10px; + //width: 270px; + //display: flex; +} + +.mark { + flex: 0 0 32px; + position: relative; + + &::after { + content: ' '; + position: absolute; + top: -38px; + right: 4px; + width: 24px; + height: 52px; + background: $green_gradient; + box-shadow: transparentize(black, 0.8) 4px 2px; + border-radius: 2px; + } +} + +.sep { + flex: 0 0 6px; + height: 6px; + width: 6px; + border-radius: 4px; + background: transparentize(black, 0.7); +} diff --git a/src/containers/node/NodeLayout/index.tsx b/src/containers/node/NodeLayout/index.tsx index de6dd97c..cd629d04 100644 --- a/src/containers/node/NodeLayout/index.tsx +++ b/src/containers/node/NodeLayout/index.tsx @@ -1,4 +1,4 @@ -import React, { FC, createElement, useEffect, useCallback } from 'react'; +import React, { FC, createElement, useEffect, useCallback, useState } from 'react'; import { RouteComponentProps } from 'react-router'; import { connect } from 'react-redux'; @@ -41,6 +41,10 @@ const NodeLayoutUnconnected: FC = ({ nodeLoadNode, nodeUpdateTags, }) => { + const [layout, setLayout] = useState({}); + + const updateLayout = useCallback(() => setLayout({}), []); + useEffect(() => { if (is_loading) return; nodeLoadNode(parseInt(id, 10), null); @@ -56,9 +60,9 @@ const NodeLayoutUnconnected: FC = ({ return ( - {block && createElement(block, { node, is_loading })} + {block && createElement(block, { node, is_loading, updateLayout, layout })} - + diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index ae60f7aa..cd13c326 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -57,7 +57,10 @@ export const NODE_TYPES = { TEXT: 'text', }; -type INodeComponents = Record, FC<{ node: INode; is_loading: boolean }>>; +type INodeComponents = Record< + ValueOf, + FC<{ node: INode; is_loading: boolean; layout: {}; updateLayout: () => void }> +>; export const NODE_COMPONENTS: INodeComponents = { [NODE_TYPES.IMAGE]: NodeImageBlock,