mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
refactored tag component
This commit is contained in:
parent
01f52bcb63
commit
ede4f4662c
16 changed files with 344 additions and 207 deletions
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { Tags } from '../Tags';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { Tags } from '~/components/tags/Tags';
|
||||
|
||||
interface IProps {
|
||||
is_editable?: boolean;
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import React, { FC, memo } from "react";
|
||||
import { Tags } from "../Tags";
|
||||
import { ITag } from "~/redux/types";
|
||||
|
||||
interface IProps {
|
||||
is_editable?: boolean;
|
||||
tags: ITag[];
|
||||
onChange?: (tags: string[]) => void;
|
||||
}
|
||||
|
||||
const NodeTagsPlaceholder: FC<IProps> = memo(
|
||||
({ is_editable, tags, onChange }) => (
|
||||
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} />
|
||||
)
|
||||
);
|
||||
|
||||
export { NodeTagsPlaceholder };
|
15
src/components/node/NodeTagsPlaceholder/index.tsx
Normal file
15
src/components/node/NodeTagsPlaceholder/index.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { Tags } from '~/components/tags/Tags';
|
||||
|
||||
interface IProps {
|
||||
is_editable?: boolean;
|
||||
tags: ITag[];
|
||||
onChange?: (tags: string[]) => void;
|
||||
}
|
||||
|
||||
const NodeTagsPlaceholder: FC<IProps> = memo(({ is_editable, tags, onChange }) => (
|
||||
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} />
|
||||
));
|
||||
|
||||
export { NodeTagsPlaceholder };
|
|
@ -1,69 +0,0 @@
|
|||
import React, { ChangeEventHandler, FC, FocusEventHandler, KeyboardEventHandler, useCallback, } from 'react';
|
||||
import * as styles from './styles.scss';
|
||||
import { ITag } from '~/redux/types';
|
||||
import classNames = require('classnames');
|
||||
|
||||
const getTagFeature = (tag: Partial<ITag>) => {
|
||||
if (tag.title.substr(0, 1) === '/') return 'green';
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
tag: Partial<ITag>;
|
||||
size?: 'normal' | 'big';
|
||||
|
||||
is_hoverable?: boolean;
|
||||
is_editing?: boolean;
|
||||
|
||||
onInput?: ChangeEventHandler<HTMLInputElement>;
|
||||
onKeyUp?: KeyboardEventHandler;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onClick?: (tag: Partial<ITag>) => void;
|
||||
}
|
||||
|
||||
const Tag: FC<IProps> = ({
|
||||
tag,
|
||||
is_hoverable,
|
||||
is_editing,
|
||||
size = 'normal',
|
||||
onInput,
|
||||
onKeyUp,
|
||||
onBlur,
|
||||
onClick,
|
||||
}) => {
|
||||
const onClickHandler = useCallback(() => {
|
||||
if (!onClick) return;
|
||||
onClick(tag);
|
||||
}, [tag, onClick]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.tag, getTagFeature(tag), size, {
|
||||
is_hoverable,
|
||||
is_editing,
|
||||
input: !!onInput,
|
||||
clickable: !!onClick,
|
||||
})}
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
<div className={styles.hole} />
|
||||
<div className={styles.title}>{tag.title}</div>
|
||||
|
||||
{onInput && (
|
||||
<input
|
||||
type="text"
|
||||
value={tag.title}
|
||||
size={1}
|
||||
placeholder="Добавить"
|
||||
maxLength={24}
|
||||
onChange={onInput}
|
||||
onKeyUp={onKeyUp}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Tag };
|
|
@ -1,120 +0,0 @@
|
|||
$big: 1.2;
|
||||
|
||||
.tag {
|
||||
@include outer_shadow();
|
||||
|
||||
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;
|
||||
|
||||
&:global(.big) {
|
||||
height: $tag_height * $big;
|
||||
font: $font_16_semibold;
|
||||
border-radius: ($tag_height * $big / 2) 3px 3px ($tag_height * $big / 2);
|
||||
|
||||
.hole {
|
||||
width: $tag_height * $big;
|
||||
height: $tag_height * $big;
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.is_hoverable) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:global(.is_editing) {
|
||||
cursor: pointer;
|
||||
background-color: lighten($tag_bg, 10%);
|
||||
}
|
||||
|
||||
&:global(.red) {
|
||||
background: $red_gradient;
|
||||
}
|
||||
|
||||
&:global(.blue) {
|
||||
background: $blue_gradient;
|
||||
}
|
||||
|
||||
&:global(.green) {
|
||||
background: $green_gradient;
|
||||
}
|
||||
|
||||
&:global(.olive) {
|
||||
background: $olive;
|
||||
color: transparentize(black, 0.4);
|
||||
}
|
||||
|
||||
&:global(.black) {
|
||||
background: transparentize(black, 0.7);
|
||||
box-shadow: none;
|
||||
color: transparentize(white, 0.6);
|
||||
font: $font_14_medium;
|
||||
|
||||
.hole::after {
|
||||
background: transparentize(white, 0.98);
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.input) {
|
||||
color: transparent !important;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
&:global(.clickable) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
outline: none;
|
||||
display: inline-flex;
|
||||
position: absolute;
|
||||
font: inherit;
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding-left: $tag_height;
|
||||
padding-right: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.hole {
|
||||
width: $tag_height;
|
||||
height: $tag_height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 $tag_height;
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
background: transparentize(black, 0.7);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import React, {
|
||||
ChangeEvent,
|
||||
FC,
|
||||
HTMLAttributes,
|
||||
KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { TagField } from '~/components/containers/TagField';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { Tag } from '~/components/node/Tag';
|
||||
import uniq from 'ramda/es/uniq';
|
||||
|
||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||
tags: Partial<ITag>[];
|
||||
is_editable?: boolean;
|
||||
onTagsChange?: (tags: string[]) => void;
|
||||
onTagClick?: (tag: Partial<ITag>) => void;
|
||||
};
|
||||
|
||||
export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, onTagClick, ...props }) => {
|
||||
const [input, setInput] = useState('');
|
||||
const [data, setData] = useState([]);
|
||||
const timer = useRef(null);
|
||||
|
||||
const [catTags, ordinaryTags] = useMemo(
|
||||
() =>
|
||||
(tags || []).reduce(
|
||||
(obj, tag) =>
|
||||
tag.title.substr(0, 1) === '/' ? [[...obj[0], tag], obj[1]] : [obj[0], [...obj[1], tag]],
|
||||
[[], []]
|
||||
),
|
||||
[tags]
|
||||
);
|
||||
|
||||
const onInput = useCallback(
|
||||
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
||||
clearTimeout(timer.current);
|
||||
setInput(value);
|
||||
},
|
||||
[setInput, timer]
|
||||
);
|
||||
|
||||
const onKeyUp = useCallback(
|
||||
({ key }: KeyboardEvent) => {
|
||||
if (key === 'Backspace' && input === '' && data.length) {
|
||||
setData(data.slice(0, data.length - 1));
|
||||
setInput(data[data.length - 1].title);
|
||||
}
|
||||
|
||||
if (key === 'Enter' || key === ',' || key === 'Comma') {
|
||||
setData(
|
||||
uniq([
|
||||
...data,
|
||||
...input
|
||||
.split(',')
|
||||
.map((title: string) =>
|
||||
title
|
||||
.trim()
|
||||
.substr(0, 32)
|
||||
.toLowerCase()
|
||||
)
|
||||
.filter(el => el.length > 0)
|
||||
.filter(el => !tags.some(tag => tag.title.trim() === el.trim()))
|
||||
.map(title => ({
|
||||
title,
|
||||
})),
|
||||
])
|
||||
);
|
||||
setInput('');
|
||||
}
|
||||
},
|
||||
[input, setInput, data, setData]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const title = input && input.trim();
|
||||
const items = (title ? [...data, { title }] : data)
|
||||
.filter(tag => tag.title.length > 0)
|
||||
.map(tag => ({
|
||||
...tag,
|
||||
title: tag.title.toLowerCase(),
|
||||
}));
|
||||
|
||||
if (!items.length) return;
|
||||
|
||||
setData(items);
|
||||
setInput('');
|
||||
onTagsChange(uniq([...tags, ...items]).map(tag => tag.title));
|
||||
}, [tags, data, onTagsChange, input, setInput]);
|
||||
|
||||
useEffect(() => {
|
||||
setData(data.filter(({ title }) => !tags.some(tag => tag.title.trim() === title.trim())));
|
||||
}, [tags]);
|
||||
|
||||
return (
|
||||
<TagField {...props}>
|
||||
{catTags.map(tag => (
|
||||
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
|
||||
))}
|
||||
|
||||
{ordinaryTags.map(tag => (
|
||||
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
|
||||
))}
|
||||
|
||||
{data.map(tag => (
|
||||
<Tag key={tag.title} tag={tag} is_editing />
|
||||
))}
|
||||
|
||||
{is_editable && (
|
||||
<Tag tag={{ title: input }} onInput={onInput} onKeyUp={onKeyUp} onBlur={onSubmit} />
|
||||
)}
|
||||
</TagField>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue