mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
refactored tag component
This commit is contained in:
parent
01f52bcb63
commit
ede4f4662c
16 changed files with 344 additions and 207 deletions
|
@ -3,4 +3,8 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&> * {
|
||||||
|
margin: 0 $gap $gap 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC, memo } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { Tags } from '../Tags';
|
|
||||||
import { ITag } from '~/redux/types';
|
import { ITag } from '~/redux/types';
|
||||||
|
import { Tags } from '~/components/tags/Tags';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_editable?: boolean;
|
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,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>
|
|
||||||
);
|
|
||||||
};
|
|
43
src/components/tags/Tag/index.tsx
Normal file
43
src/components/tags/Tag/index.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { FC, FocusEventHandler, useCallback, } from 'react';
|
||||||
|
import * as styles from './styles.scss';
|
||||||
|
import { ITag } from '~/redux/types';
|
||||||
|
import { TagWrapper } from '~/components/tags/TagWrapper';
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||||
|
onClick?: (tag: Partial<ITag>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tag: FC<IProps> = ({ tag, is_hoverable, is_editing, size = 'normal', onBlur, onClick }) => {
|
||||||
|
const onClickHandler = useCallback(() => {
|
||||||
|
if (!onClick) return;
|
||||||
|
onClick(tag);
|
||||||
|
}, [tag, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<TagWrapper
|
||||||
|
feature={getTagFeature(tag)}
|
||||||
|
size={size}
|
||||||
|
is_hoverable={is_hoverable}
|
||||||
|
is_editing={is_editing}
|
||||||
|
onClick={onClick && onClickHandler}
|
||||||
|
title={tag.title}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Tag };
|
4
src/components/tags/Tag/styles.scss
Normal file
4
src/components/tags/Tag/styles.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.wrap {
|
||||||
|
background-color: blue;
|
||||||
|
position: relative;
|
||||||
|
}
|
9
src/components/tags/TagAutocomplete/index.tsx
Normal file
9
src/components/tags/TagAutocomplete/index.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
interface IProps {}
|
||||||
|
|
||||||
|
const TagAutocomplete: FC<IProps> = () => <div className={classNames(styles.window)}>auto</div>;
|
||||||
|
|
||||||
|
export { TagAutocomplete };
|
11
src/components/tags/TagAutocomplete/styles.module.scss
Normal file
11
src/components/tags/TagAutocomplete/styles.module.scss
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.window {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: calc(90vw - 20px);
|
||||||
|
max-width: 300px;
|
||||||
|
background: red;
|
||||||
|
height: 100px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
121
src/components/tags/TagInput/index.tsx
Normal file
121
src/components/tags/TagInput/index.tsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
FC,
|
||||||
|
FocusEventHandler,
|
||||||
|
KeyboardEvent,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
|
||||||
|
import { TagWrapper } from '~/components/tags/TagWrapper';
|
||||||
|
|
||||||
|
const placeholder = 'Добавить';
|
||||||
|
|
||||||
|
const prepareInput = (input: string): string[] => {
|
||||||
|
return input
|
||||||
|
.split(',')
|
||||||
|
.map((title: string) =>
|
||||||
|
title
|
||||||
|
.trim()
|
||||||
|
.substr(0, 32)
|
||||||
|
.toLowerCase()
|
||||||
|
)
|
||||||
|
.filter(el => el.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onAppend: (tags: string[]) => void;
|
||||||
|
onClearTag: () => string | undefined;
|
||||||
|
onSubmit: (last: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagInput: FC<IProps> = ({ onAppend, onClearTag, onSubmit }) => {
|
||||||
|
const [focused, setFocused] = useState(false);
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const onInput = useCallback(
|
||||||
|
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!value.trim()) {
|
||||||
|
setInput(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = prepareInput(value);
|
||||||
|
|
||||||
|
if (items.length > 1) {
|
||||||
|
onAppend(items.slice(0, items.length - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
setInput(items[items.length - 1]);
|
||||||
|
},
|
||||||
|
[setInput]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onKeyUp = useCallback(
|
||||||
|
({ key }: KeyboardEvent) => {
|
||||||
|
if (key === 'Escape' && ref.current) {
|
||||||
|
setInput('');
|
||||||
|
ref.current.blur();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Backspace' && input === '') {
|
||||||
|
setInput(onClearTag() || '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Enter' || key === ',' || key === 'Comma') {
|
||||||
|
const created = prepareInput(input);
|
||||||
|
|
||||||
|
if (created.length) {
|
||||||
|
onAppend(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInput('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Enter' && ref.current) {
|
||||||
|
ref.current.blur();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[input, setInput, onClearTag, onAppend, onSubmit, ref.current]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFocus = useCallback(() => setFocused(true), []);
|
||||||
|
const onBlur = useCallback<FocusEventHandler<HTMLInputElement>>(() => {
|
||||||
|
setFocused(false);
|
||||||
|
|
||||||
|
if (input.trim()) {
|
||||||
|
const created = prepareInput(input);
|
||||||
|
onAppend(created);
|
||||||
|
setInput('');
|
||||||
|
onSubmit(created);
|
||||||
|
}
|
||||||
|
}, [input, onAppend, setInput, onSubmit]);
|
||||||
|
|
||||||
|
const feature = useMemo(() => (input.substr(0, 1) === '/' ? 'green' : ''), [input]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagWrapper title={input || placeholder} has_input={true} feature={feature}>
|
||||||
|
{onInput && <TagAutocomplete />}
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
size={1}
|
||||||
|
placeholder={placeholder}
|
||||||
|
maxLength={24}
|
||||||
|
onChange={onInput}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onFocus={onFocus}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
</TagWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TagInput };
|
40
src/components/tags/TagWrapper/index.tsx
Normal file
40
src/components/tags/TagWrapper/index.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
feature?: string;
|
||||||
|
size?: string;
|
||||||
|
is_hoverable?: boolean;
|
||||||
|
is_editing?: boolean;
|
||||||
|
has_input?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagWrapper: FC<IProps> = ({
|
||||||
|
children,
|
||||||
|
feature,
|
||||||
|
size,
|
||||||
|
is_hoverable,
|
||||||
|
is_editing,
|
||||||
|
has_input,
|
||||||
|
onClick,
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { TagWrapper };
|
|
@ -14,8 +14,9 @@ $big: 1.2;
|
||||||
font: $font_14_semibold;
|
font: $font_14_semibold;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding: 0 8px 0 0;
|
padding: 0 8px 0 0;
|
||||||
margin: 0 $gap $gap 0;
|
//margin: 0 $gap $gap 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 4;
|
||||||
|
|
||||||
&:global(.big) {
|
&:global(.big) {
|
||||||
height: $tag_height * $big;
|
height: $tag_height * $big;
|
||||||
|
@ -88,6 +89,7 @@ $big: 1.2;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 100px;
|
||||||
padding-left: $tag_height;
|
padding-left: $tag_height;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -118,3 +120,4 @@ $big: 1.2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
90
src/components/tags/Tags/index.tsx
Normal file
90
src/components/tags/Tags/index.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { FC, HTMLAttributes, useCallback, useEffect, useMemo, useState, } from 'react';
|
||||||
|
import { TagField } from '~/components/containers/TagField';
|
||||||
|
import { ITag } from '~/redux/types';
|
||||||
|
import uniq from 'ramda/es/uniq';
|
||||||
|
import { Tag } from '~/components/tags/Tag';
|
||||||
|
import { TagInput } from '~/components/tags/TagInput';
|
||||||
|
|
||||||
|
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 [data, setData] = useState<string[]>([]);
|
||||||
|
|
||||||
|
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 onSubmit = useCallback(
|
||||||
|
(last: string[]) => {
|
||||||
|
const exist = tags.map(tag => tag.title);
|
||||||
|
onTagsChange(uniq([...exist, ...data, ...last]));
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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]);
|
||||||
|
|
||||||
|
const onAppendTag = useCallback(
|
||||||
|
(created: string[]) => {
|
||||||
|
setData(uniq([...data, ...created]).filter(title => !tags.some(it => it.title === title)));
|
||||||
|
},
|
||||||
|
[data, setData, tags]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClearTag = useCallback((): string | undefined => {
|
||||||
|
if (!data.length) return;
|
||||||
|
const last = data[data.length - 1];
|
||||||
|
setData(data.slice(0, data.length - 1));
|
||||||
|
return last;
|
||||||
|
}, [data, setData]);
|
||||||
|
|
||||||
|
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(title => (
|
||||||
|
<Tag key={title} tag={{ title }} is_editing />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{is_editable && (
|
||||||
|
<TagInput onAppend={onAppendTag} onClearTag={onClearTag} onSubmit={onSubmit} />
|
||||||
|
)}
|
||||||
|
</TagField>
|
||||||
|
);
|
||||||
|
};
|
|
@ -2,7 +2,6 @@ import React, { FC, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
|
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { useHistory, useRouteMatch } from 'react-router';
|
import { useHistory, useRouteMatch } from 'react-router';
|
||||||
import { Tag } from '~/components/node/Tag';
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { TagSidebarList } from '~/components/sidebar/TagSidebarList';
|
import { TagSidebarList } from '~/components/sidebar/TagSidebarList';
|
||||||
|
@ -11,6 +10,7 @@ import { selectTagNodes } from '~/redux/tag/selectors';
|
||||||
import * as ACTIONS from '~/redux/tag/actions';
|
import * as ACTIONS from '~/redux/tag/actions';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import { InfiniteScroll } from '~/components/containers/InfiniteScroll';
|
import { InfiniteScroll } from '~/components/containers/InfiniteScroll';
|
||||||
|
import { Tag } from '~/components/tags/Tag';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
nodes: selectTagNodes(state),
|
nodes: selectTagNodes(state),
|
||||||
|
|
|
@ -176,6 +176,7 @@ module.exports = () => {
|
||||||
contentBase: 'dist',
|
contentBase: 'dist',
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
hot: true,
|
hot: true,
|
||||||
|
open: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue