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:
parent
0a75feef8d
commit
04a7b28a53
16 changed files with 195 additions and 39 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 => (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue