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

added tag deletion interface

This commit is contained in:
Fedor Katurov 2021-09-28 15:39:29 +07:00
parent 0a75feef8d
commit 04a7b28a53
16 changed files with 195 additions and 39 deletions

View file

@ -27,7 +27,7 @@ const LabTags: FC<IProps> = ({ tags, isLoading }) => {
return (
<div className={styles.tags}>
{tags.slice(0, 10).map(tag => (
<Tag tag={tag} key={tag.id} />
<Tag tag={tag} key={tag.ID} />
))}
</div>
);

View file

@ -16,6 +16,7 @@ import { NodeAuthorBlock } from '~/components/node/NodeAuthorBlock';
interface IProps {
node: INode;
canEdit: boolean;
isLoading: boolean;
commentsOrder: 'ASC' | 'DESC';
comments: IComment[];
@ -26,6 +27,7 @@ interface IProps {
const NodeBottomBlock: FC<IProps> = ({
node,
canEdit,
isLoading,
isLoadingComments,
comments,
@ -66,7 +68,7 @@ const NodeBottomBlock: FC<IProps> = ({
<NodeAuthorBlock node={node} />
</div>
<div className={styles.left_item}>
<NodeTagsBlock node={node} isLoading={isLoading} />
<NodeTagsBlock node={node} canEdit={canEdit} isLoading={isLoading} />
</div>
<div className={styles.left_item}>
<NodeRelatedBlock isLoading={isLoading} node={node} related={related} />

View file

@ -3,16 +3,27 @@ import { ITag } from '~/redux/types';
import { Tags } from '~/components/tags/Tags';
interface IProps {
is_deletable?: boolean;
is_editable?: boolean;
tags: ITag[];
onChange?: (tags: string[]) => void;
onTagClick?: (tag: Partial<ITag>) => void;
onTagDelete?: (id: ITag['ID']) => void;
}
const NodeTags: FC<IProps> = memo(({ is_editable, tags, onChange, onTagClick }) => {
return (
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} onTagClick={onTagClick} />
);
});
const NodeTags: FC<IProps> = memo(
({ is_editable, is_deletable, tags, onChange, onTagClick, onTagDelete }) => {
return (
<Tags
tags={tags}
is_editable={is_editable}
onTagsChange={onChange}
onTagClick={onTagClick}
onTagDelete={onTagDelete}
is_deletable={is_deletable}
/>
);
}
);
export { NodeTags };

View file

@ -1,7 +1,7 @@
import React, { FC, useCallback } from 'react';
import { INode, ITag } from '~/redux/types';
import { URLS } from '~/constants/urls';
import { nodeUpdateTags } from '~/redux/node/actions';
import { nodeDeleteTag, nodeUpdateTags } from '~/redux/node/actions';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router';
import { NodeTags } from '~/components/node/NodeTags';
@ -9,10 +9,11 @@ import { useUser } from '~/utils/hooks/user/userUser';
interface IProps {
node: INode;
canEdit: boolean;
isLoading: boolean;
}
const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
const NodeTagsBlock: FC<IProps> = ({ node, canEdit, isLoading }) => {
const dispatch = useDispatch();
const history = useHistory();
const { is_user } = useUser();
@ -35,6 +36,13 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
[history, node]
);
const onTagDelete = useCallback(
(tagId: ITag['ID']) => {
dispatch(nodeDeleteTag(node.id, tagId));
},
[dispatch, node.id]
);
if (isLoading) {
return null;
}
@ -42,9 +50,11 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
return (
<NodeTags
is_editable={is_user}
is_deletable={canEdit}
tags={node.tags}
onChange={onTagsChange}
onTagClick={onTagClick}
onTagDelete={onTagDelete}
/>
);
};

View file

@ -12,26 +12,46 @@ interface IProps {
tag: Partial<ITag>;
size?: 'normal' | 'big';
is_deletable?: boolean;
is_hoverable?: boolean;
is_editing?: boolean;
onBlur?: FocusEventHandler<HTMLInputElement>;
onClick?: (tag: Partial<ITag>) => void;
onDelete?: (id: ITag['ID']) => void;
}
const Tag: FC<IProps> = ({ tag, is_hoverable, is_editing, size = 'normal', onBlur, onClick }) => {
const Tag: FC<IProps> = ({
tag,
is_deletable,
is_hoverable,
is_editing,
size = 'normal',
onClick,
onDelete,
}) => {
const onClickHandler = useCallback(() => {
if (!onClick) return;
onClick(tag);
}, [tag, onClick]);
const onDeleteHandler = useCallback(() => {
if (!onDelete) {
return;
}
onDelete(tag.ID!);
}, [onDelete, tag]);
return (
<TagWrapper
feature={getTagFeature(tag)}
size={size}
is_deletable={is_deletable}
is_hoverable={is_hoverable}
is_editing={is_editing}
onClick={onClick && onClickHandler}
onDelete={onDeleteHandler}
title={tag.title}
/>
);

View file

@ -1,14 +1,18 @@
import React, { FC } from 'react';
import React, { FC, useCallback, useState } from 'react';
import classNames from 'classnames';
import styles from './styles.module.scss';
import { Manager, Popper, Reference } from 'react-popper';
import { Icon } from '~/components/input/Icon';
interface IProps {
feature?: string;
size?: string;
is_deletable?: boolean;
is_hoverable?: boolean;
is_editing?: boolean;
has_input?: boolean;
onClick?: () => void;
onDelete?: () => void;
title?: string;
}
@ -16,25 +20,39 @@ const TagWrapper: FC<IProps> = ({
children,
feature,
size,
is_deletable,
is_hoverable,
is_editing,
has_input,
onClick,
onDelete,
title = '',
}) => (
<div
className={classNames(styles.tag, feature, size, {
is_hoverable,
is_editing,
input: has_input,
clickable: onClick,
})}
onClick={onClick}
>
<div className={styles.hole} />
<div className={styles.title}>{title}</div>
{children}
</div>
);
}) => {
const deletable = is_deletable && !is_editing && !has_input;
return (
<div
className={classNames(styles.tag, feature, size, {
is_hoverable,
is_editing,
deletable,
input: has_input,
clickable: onClick,
})}
>
<div className={styles.content} onClick={onClick}>
<div className={styles.hole} />
<div className={styles.title}>{title}</div>
{children}
</div>
{deletable && (
<button type="button" className={styles.delete} onClick={onDelete}>
<Icon icon="close" size={20} />
</button>
)}
</div>
);
};
export { TagWrapper };

View file

@ -5,21 +5,21 @@ $big: 1.2;
.tag {
@include outer_shadow();
overflow: hidden;
cursor: default;
height: $tag_height;
background: $tag_bg;
display: flex;
flex-direction: row;
align-items: center;
justify-content: stretch;
border-radius: ($tag_height / 2) 3px 3px ($tag_height / 2);
font: $font_14_semibold;
align-self: flex-start;
padding: 0 8px 0 0;
//margin: 0 $gap $gap 0;
position: relative;
z-index: 12;
&:hover {
z-index: 40;
}
&:global(.big) {
height: $tag_height * $big;
font: $font_16_semibold;
@ -98,6 +98,19 @@ $big: 1.2;
}
}
.content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: stretch;
width: 100%;
transition: transform 250ms;
:hover:global(.deletable) > & {
transform: translate(-32px, 0);
}
}
.hole {
width: $tag_height;
height: $tag_height;
@ -123,3 +136,28 @@ $big: 1.2;
text-overflow: ellipsis;
}
button.delete {
@include inner_shadow;
width: 32px;
height: 100%;
z-index: 24;
background: $red;
border: none;
padding: 0;
margin: 0 0 0 -5px;
border-radius: 0;
position: absolute;
right: -32px;
top: 0;
transition: transform 250ms;
transform: translate(0, 0);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
:hover > & {
transform: translate(-32px, 0);
}
}

View file

@ -8,12 +8,22 @@ import { separateTags } from '~/utils/tag';
type IProps = HTMLAttributes<HTMLDivElement> & {
tags: Partial<ITag>[];
is_deletable?: boolean;
is_editable?: boolean;
onTagsChange?: (tags: string[]) => void;
onTagClick?: (tag: Partial<ITag>) => void;
onTagDelete?: (id: ITag['ID']) => void;
};
export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, onTagClick, ...props }) => {
export const Tags: FC<IProps> = ({
tags,
is_deletable,
is_editable,
onTagsChange,
onTagClick,
onTagDelete,
...props
}) => {
const [data, setData] = useState<string[]>([]);
const [catTags, ordinaryTags] = useMemo(() => separateTags(tags), [tags]);
@ -56,11 +66,23 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, onTagClick,
return (
<TagField {...props}>
{catTags.map(tag => (
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
<Tag
key={tag.title}
tag={tag}
onClick={onTagClick}
is_deletable={is_deletable}
onDelete={onTagDelete}
/>
))}
{ordinaryTags.map(tag => (
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
<Tag
key={tag.title}
tag={tag}
onClick={onTagClick}
is_deletable={is_deletable}
onDelete={onTagDelete}
/>
))}
{data.map(title => (