1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

added user profile popup

This commit is contained in:
Fedor Katurov 2021-09-22 17:54:51 +07:00
parent 43450dff54
commit 1242c04587
10 changed files with 162 additions and 57 deletions

View file

@ -31,10 +31,10 @@ const Comment: FC<IProps> = memo(
return ( return (
<CommentWrapper <CommentWrapper
className={className} className={className}
is_empty={is_empty} isEmpty={is_empty}
is_loading={is_loading} isLoading={is_loading}
user={comment_group.user} user={comment_group.user}
is_same={is_same} isSame={is_same}
{...props} {...props}
> >
<div className={styles.wrap}> <div className={styles.wrap}>

View file

@ -1,27 +1,59 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback, useState } from 'react';
import { getURLFromString } from '~/utils/dom'; import { IUser } from '~/redux/auth/types';
import { PRESETS } from '~/constants/urls'; import { Avatar } from '~/components/common/Avatar';
import { path } from 'ramda';
import { Manager, Popper, Reference } from 'react-popper';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import classNames from 'classnames'; import { useRandomPhrase } from '~/constants/phrases';
import { openUserProfile } from '~/utils/user';
interface Props { interface Props {
url?: string; user: IUser;
username?: string; withDetails: boolean;
size?: number;
className?: string; className?: string;
} }
const CommentAvatar: FC<Props> = ({ url, username, size, className }) => { const modifiers = [
const backgroundImage = !!url ? `url('${getURLFromString(url, PRESETS.avatar)}')` : undefined; {
const onOpenProfile = useCallback(() => openUserProfile(username), [username]); name: 'offset',
options: {
offset: [0, 10],
},
},
];
const CommentAvatar: FC<Props> = ({ user, withDetails, className }) => {
const [hovered, setHovered] = useState(false);
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
const onMouseOver = useCallback(() => setHovered(true), [setHovered]);
const onMouseOut = useCallback(() => setHovered(false), [setHovered]);
return ( return (
<div <Manager>
className={classNames(styles.avatar, className)} <Reference>
style={{ backgroundImage }} {({ ref }) => (
onClick={onOpenProfile} <Avatar
/> url={path(['photo', 'url'], user)}
username={user.username}
className={className}
innerRef={ref}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
/>
)}
</Reference>
{hovered && withDetails && (
<Popper placement="right" modifiers={modifiers}>
{({ style, ref }) => (
<div style={style} ref={ref} className={styles.popper}>
<h4 className={styles.username}>{user.fullname || user.username}</h4>
<div className={styles.description}>{user.description || randomPhrase}</div>
</div>
)}
</Popper>
)}
</Manager>
); );
}; };

View file

@ -1,19 +1,33 @@
@import "~/styles/variables"; @import "~/styles/variables.scss";
.avatar { @keyframes appear {
0% { opacity: 0 }
100% { opacity: 1 }
}
.popper {
@include outer_shadow; @include outer_shadow;
width: $comment_height; background-color: darken($content_bg, 4%);
height: $comment_height; padding: $gap;
background-color: transparentize(black, 0.9); box-sizing:border-box;
flex-shrink: 0; z-index: 4;
overflow: hidden; touch-action: none;
pointer-events: none;
border-radius: $radius; border-radius: $radius;
background-position: center; animation: appear forwards 250ms;
background-size: cover; }
cursor: pointer;
.username {
img { font: $font_18_semibold;
object-fit: cover; line-height: 1.25em;
} }
.description {
@include clamp(2, 12px);
font: $font_12_regular;
opacity: 0.5;
margin-top: 3px;
line-height: 1.25em;
} }

View file

@ -0,0 +1,31 @@
import React, { FC, useCallback } from 'react';
import { getURLFromString } from '~/utils/dom';
import { PRESETS } from '~/constants/urls';
import styles from './styles.module.scss';
import classNames from 'classnames';
import { openUserProfile } from '~/utils/user';
import { DivProps } from '~/utils/types';
interface Props extends DivProps {
url?: string;
username?: string;
size?: number;
innerRef?: React.Ref<any>;
}
const Avatar: FC<Props> = ({ url, username, size, className, innerRef, ...rest }) => {
const backgroundImage = !!url ? `url('${getURLFromString(url, PRESETS.avatar)}')` : undefined;
const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
return (
<div
{...rest}
className={classNames(styles.avatar, className)}
style={{ backgroundImage }}
onClick={onOpenProfile}
ref={innerRef}
/>
);
};
export { Avatar };

View file

@ -0,0 +1,19 @@
@import "~/styles/variables";
.avatar {
@include outer_shadow;
width: $comment_height;
height: $comment_height;
background-color: transparentize(black, 0.9);
flex-shrink: 0;
overflow: hidden;
border-radius: $radius;
background-position: center;
background-size: cover;
cursor: pointer;
img {
object-fit: cover;
}
}

View file

@ -1,37 +1,40 @@
import React, { FC, HTMLAttributes } from 'react'; import React, { FC } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { IUser } from '~/redux/auth/types'; import { IUser } from '~/redux/auth/types';
import { path } from 'ramda'; import { path } from 'ramda';
import { CommentAvatar } from '~/components/comment/CommentAvatar'; import { CommentAvatar } from '~/components/comment/CommentAvatar';
import { Card } from '~/components/containers/Card'; import { DivProps } from '~/utils/types';
type IProps = HTMLAttributes<HTMLDivElement> & { type IProps = DivProps & {
user: IUser; user: IUser;
is_empty?: boolean; isEmpty?: boolean;
is_loading?: boolean; isLoading?: boolean;
is_same?: boolean; isSame?: boolean;
isForm?: boolean;
}; };
const CommentWrapper: FC<IProps> = ({ const CommentWrapper: FC<IProps> = ({
// photo,
children,
is_empty,
is_loading,
className,
is_same,
user, user,
className,
isEmpty,
isLoading,
isSame,
isForm,
children,
...props ...props
}) => ( }) => (
<div className={classNames(styles.wrap, className, { is_empty, is_loading, is_same })} {...props}> <div
className={classNames(styles.wrap, className, {
is_empty: isEmpty,
is_loading: isLoading,
is_same: isSame,
})}
{...props}
>
<div className={styles.thumb}> <div className={styles.thumb}>
<CommentAvatar <CommentAvatar user={user} className={styles.thumb_image} withDetails={!isForm} />
url={path(['photo', 'url'], user)}
username={user.username}
className={styles.thumb_image}
/>
<div className={styles.thumb_user}>~{path(['username'], user)}</div> <div className={styles.thumb_user}>~{path(['username'], user)}</div>
</div> </div>

View file

@ -1,7 +1,7 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { INode } from '~/redux/types'; import { INode } from '~/redux/types';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { CommentAvatar } from '~/components/comment/CommentAvatar'; import { Avatar } from '~/components/common/Avatar';
import { openUserProfile } from '~/utils/user'; import { openUserProfile } from '~/utils/user';
import { useRandomPhrase } from '~/constants/phrases'; import { useRandomPhrase } from '~/constants/phrases';
@ -24,7 +24,7 @@ const NodeAuthorBlock: FC<Props> = ({ node }) => {
return ( return (
<div className={styles.block} onClick={onOpenProfile}> <div className={styles.block} onClick={onOpenProfile}>
<CommentAvatar username={username} url={photo?.url} className={styles.avatar} /> <Avatar username={username} url={photo?.url} className={styles.avatar} />
<div className={styles.info}> <div className={styles.info}>
<div className={styles.username}>{fullname || username}</div> <div className={styles.username}>{fullname || username}</div>

View file

@ -16,7 +16,7 @@ type IProps = ReturnType<typeof mapStateToProps> & {
const NodeCommentFormUnconnected: FC<IProps> = ({ user, isBefore, nodeId }) => { const NodeCommentFormUnconnected: FC<IProps> = ({ user, isBefore, nodeId }) => {
return ( return (
<CommentWrapper user={user}> <CommentWrapper user={user} isForm>
<CommentForm nodeId={nodeId} /> <CommentForm nodeId={nodeId} />
</CommentWrapper> </CommentWrapper>
); );

View file

@ -5,7 +5,7 @@ import { INode } from '~/redux/types';
import { PRESETS, URLS } from '~/constants/urls'; import { PRESETS, URLS } from '~/constants/urls';
import { RouteComponentProps, withRouter } from 'react-router'; import { RouteComponentProps, withRouter } from 'react-router';
import { getURL, stringToColour } from '~/utils/dom'; import { getURL, stringToColour } from '~/utils/dom';
import { CommentAvatar } from '~/components/comment/CommentAvatar'; import { Avatar } from '~/components/common/Avatar';
type IProps = RouteComponentProps & { type IProps = RouteComponentProps & {
item: Partial<INode>; item: Partial<INode>;
@ -69,7 +69,7 @@ const NodeRelatedItemUnconnected: FC<IProps> = memo(({ item, history }) => {
onClick={onClick} onClick={onClick}
ref={ref} ref={ref}
> >
<CommentAvatar <Avatar
username={item.title} username={item.title}
url={item.thumbnail} url={item.thumbnail}
className={classNames(styles.thumb, { [styles.is_loaded]: is_loaded })} className={classNames(styles.thumb, { [styles.is_loaded]: is_loaded })}

6
src/utils/types.ts Normal file
View file

@ -0,0 +1,6 @@
import React from 'react';
export type DivProps = React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;