mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
floating node panel
This commit is contained in:
parent
9e25b4e2b0
commit
336582b3d6
7 changed files with 200 additions and 113 deletions
|
@ -18,9 +18,11 @@ import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
|||
interface IProps {
|
||||
is_loading: boolean;
|
||||
node: INode;
|
||||
layout: {};
|
||||
updateLayout: () => void;
|
||||
}
|
||||
|
||||
const NodeImageBlock: FC<IProps> = ({ node, is_loading }) => {
|
||||
const NodeImageBlock: FC<IProps> = ({ 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<IProps> = ({ node, is_loading }) => {
|
|||
loaded,
|
||||
]);
|
||||
|
||||
useEffect(() => updateLayout(), [loaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!refs || !refs.current[current] || !loaded[current]) return setHeight(320);
|
||||
|
||||
|
|
|
@ -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<IProps> = ({ node: { title, user } }) => (
|
||||
<div className={styles.wrap}>
|
||||
<Group horizontal className={styles.panel}>
|
||||
<Filler>
|
||||
<div className={styles.title}>{title || '...'}</div>
|
||||
{user && user.username && <div className={styles.name}>~ {user.username}</div>}
|
||||
</Filler>
|
||||
</Group>
|
||||
const NodePanel: FC<IProps> = ({ node, layout }) => {
|
||||
const [stack, setStack] = useState(false);
|
||||
|
||||
<div className={styles.buttons}>
|
||||
<Icon icon="edit" size={24} />
|
||||
const ref = useRef(null);
|
||||
const getPlace = useCallback(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
<div className={styles.sep} />
|
||||
const { offsetTop } = ref.current;
|
||||
const { height } = ref.current.getBoundingClientRect();
|
||||
const { scrollY, innerHeight } = window;
|
||||
|
||||
<Icon icon="heart" size={24} />
|
||||
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 (
|
||||
<div className={styles.place} ref={ref}>
|
||||
{stack ? (
|
||||
createPortal(<NodePanelInner node={node} stack />, document.body)
|
||||
) : (
|
||||
<NodePanelInner node={node} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export { NodePanel };
|
||||
|
||||
// <div className={styles.mark} />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
39
src/components/node/NodePanelInner/index.tsx
Normal file
39
src/components/node/NodePanelInner/index.tsx
Normal file
|
@ -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<IProps> = ({ node: { title, user }, stack }) => {
|
||||
return (
|
||||
<div className={classNames(styles.wrap, { stack })}>
|
||||
<div className={styles.content}>
|
||||
<Group horizontal className={styles.panel}>
|
||||
<Filler>
|
||||
<div className={styles.title}>{title || '...'}</div>
|
||||
{user && user.username && <div className={styles.name}>~ {user.username}</div>}
|
||||
</Filler>
|
||||
</Group>
|
||||
|
||||
<div className={styles.buttons}>
|
||||
<Icon icon="edit" size={24} />
|
||||
|
||||
<div className={styles.sep} />
|
||||
|
||||
<Icon icon="heart" size={24} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { NodePanelInner };
|
||||
|
||||
// <div className={styles.mark} />
|
107
src/components/node/NodePanelInner/styles.scss
Normal file
107
src/components/node/NodePanelInner/styles.scss
Normal file
|
@ -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);
|
||||
}
|
|
@ -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<IProps> = ({
|
|||
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<IProps> = ({
|
|||
|
||||
return (
|
||||
<Card className={styles.node} seamless>
|
||||
{block && createElement(block, { node, is_loading })}
|
||||
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
||||
|
||||
<NodePanel node={node} />
|
||||
<NodePanel node={node} layout={layout} />
|
||||
|
||||
<Group>
|
||||
<Padder>
|
||||
|
|
|
@ -57,7 +57,10 @@ export const NODE_TYPES = {
|
|||
TEXT: 'text',
|
||||
};
|
||||
|
||||
type INodeComponents = Record<ValueOf<typeof NODE_TYPES>, FC<{ node: INode; is_loading: boolean }>>;
|
||||
type INodeComponents = Record<
|
||||
ValueOf<typeof NODE_TYPES>,
|
||||
FC<{ node: INode; is_loading: boolean; layout: {}; updateLayout: () => void }>
|
||||
>;
|
||||
|
||||
export const NODE_COMPONENTS: INodeComponents = {
|
||||
[NODE_TYPES.IMAGE]: NodeImageBlock,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue