mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
removed tag reducer
This commit is contained in:
parent
11b39b8766
commit
31e433af3e
17 changed files with 41 additions and 192 deletions
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { Tags } from '~/components/tags/Tags';
|
||||
import { Tags } from '~/containers/tags/Tags';
|
||||
|
||||
interface IProps {
|
||||
is_deletable?: boolean;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { Tags } from '~/components/tags/Tags';
|
||||
import { Tags } from '~/containers/tags/Tags';
|
||||
|
||||
interface IProps {
|
||||
is_editable?: boolean;
|
||||
|
|
|
@ -1,34 +1,23 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState, VFC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import * as TAG_ACTIONS from '~/redux/tag/actions';
|
||||
import { selectTagAutocomplete } from '~/redux/tag/selectors';
|
||||
import { separateTagOptions } from '~/utils/tag';
|
||||
import { TagAutocompleteRow } from '~/components/tags/TagAutocompleteRow';
|
||||
import { usePopper } from 'react-popper';
|
||||
|
||||
const mapStateToProps = selectTagAutocomplete;
|
||||
const mapDispatchToProps = {
|
||||
tagSetAutocomplete: TAG_ACTIONS.tagSetAutocomplete,
|
||||
tagLoadAutocomplete: TAG_ACTIONS.tagLoadAutocomplete,
|
||||
};
|
||||
interface TagAutocompleteProps {
|
||||
exclude: string[];
|
||||
input: HTMLInputElement;
|
||||
onSelect: (val: string) => void;
|
||||
search: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
type Props = ReturnType<typeof mapStateToProps> &
|
||||
typeof mapDispatchToProps & {
|
||||
exclude: string[];
|
||||
input: HTMLInputElement;
|
||||
onSelect: (val: string) => void;
|
||||
search: string;
|
||||
};
|
||||
|
||||
const TagAutocompleteUnconnected: FC<Props> = ({
|
||||
const TagAutocomplete: VFC<TagAutocompleteProps> = ({
|
||||
exclude,
|
||||
input,
|
||||
onSelect,
|
||||
search,
|
||||
tagSetAutocomplete,
|
||||
tagLoadAutocomplete,
|
||||
options,
|
||||
}) => {
|
||||
const [selected, setSelected] = useState(-1);
|
||||
|
@ -80,19 +69,6 @@ const TagAutocompleteUnconnected: FC<Props> = ({
|
|||
return () => input.removeEventListener('keydown', onKeyDown);
|
||||
}, [input, onKeyDown]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(-1);
|
||||
tagLoadAutocomplete(search, exclude);
|
||||
}, [exclude, search, tagLoadAutocomplete]);
|
||||
|
||||
useEffect(() => {
|
||||
tagSetAutocomplete({ options: [] });
|
||||
|
||||
return () => {
|
||||
tagSetAutocomplete({ options: [] });
|
||||
};
|
||||
}, [tagSetAutocomplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scroll.current || !scroll.current?.children[selected + 1]) return;
|
||||
const el = scroll.current?.children[selected + 1] as HTMLDivElement;
|
||||
|
@ -143,6 +119,4 @@ const TagAutocompleteUnconnected: FC<Props> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const TagAutocomplete = connect(mapStateToProps, mapDispatchToProps)(TagAutocompleteUnconnected);
|
||||
|
||||
export { TagAutocomplete };
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
import React, { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
|
||||
import { TagWrapper } from '~/components/tags/TagWrapper';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
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;
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [input, setInput] = useState('');
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const wrapper = useRef<HTMLDivElement>(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] || '');
|
||||
},
|
||||
[onAppend]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
({ key }) => {
|
||||
if (key === 'Escape' && ref.current) {
|
||||
setInput('');
|
||||
ref.current.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'Backspace' && input === '') {
|
||||
setInput(onClearTag() || '');
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === ',' || key === 'Comma') {
|
||||
const created = prepareInput(input);
|
||||
|
||||
if (created.length) {
|
||||
onAppend(created);
|
||||
}
|
||||
|
||||
setInput('');
|
||||
}
|
||||
},
|
||||
[input, setInput, onClearTag, onAppend]
|
||||
);
|
||||
|
||||
const onFocus = useCallback(() => setFocused(true), []);
|
||||
const onBlur = useCallback(
|
||||
event => {
|
||||
if (!wrapper.current || !ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wrapper.current.contains(event.target)) {
|
||||
ref.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setFocused(false);
|
||||
|
||||
if (input.trim()) {
|
||||
setInput('');
|
||||
}
|
||||
|
||||
onSubmit([]);
|
||||
},
|
||||
[input, setInput, onSubmit]
|
||||
);
|
||||
|
||||
const onAutocompleteSelect = useCallback(
|
||||
(val: string) => {
|
||||
onAppend([val]);
|
||||
setInput('');
|
||||
},
|
||||
[onAppend, setInput]
|
||||
);
|
||||
|
||||
const feature = useMemo(() => (input?.substr(0, 1) === '/' ? 'green' : ''), [input]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!focused) return;
|
||||
|
||||
document.addEventListener('click', onBlur);
|
||||
return () => document.removeEventListener('click', onBlur);
|
||||
}, [onBlur, focused]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap} ref={wrapper}>
|
||||
<TagWrapper title={input || placeholder} has_input={true} feature={feature}>
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
size={1}
|
||||
placeholder={placeholder}
|
||||
maxLength={24}
|
||||
onChange={onInput}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={onFocus}
|
||||
ref={ref}
|
||||
/>
|
||||
</TagWrapper>
|
||||
|
||||
{focused && input?.length > 0 && ref.current && (
|
||||
<TagAutocomplete
|
||||
exclude={exclude}
|
||||
input={ref.current}
|
||||
onSelect={onAutocompleteSelect}
|
||||
search={input}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { TagInput };
|
|
@ -1,6 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
import React, { FC, HTMLAttributes, useCallback, useMemo, useState } from 'react';
|
||||
import { TagField } from '~/components/containers/TagField';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { uniq } from 'ramda';
|
||||
import { Tag } from '~/components/tags/Tag';
|
||||
import { TagInput } from '~/components/tags/TagInput';
|
||||
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_deletable,
|
||||
is_editable,
|
||||
onTagsChange,
|
||||
onTagClick,
|
||||
onTagDelete,
|
||||
...props
|
||||
}) => {
|
||||
const [data, setData] = useState<string[]>([]);
|
||||
|
||||
const [catTags, ordinaryTags] = useMemo(() => separateTags(tags), [tags]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(last: string[]) => {
|
||||
if (!onTagsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exist = tags.map(tag => tag.title);
|
||||
const uniqueTags = uniq([...exist, ...data, ...last]).filter(el => el) as string[];
|
||||
|
||||
if (uniqueTags.length === exist.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
onTagsChange(uniqueTags);
|
||||
setData([]);
|
||||
},
|
||||
[data, onTagsChange, tags]
|
||||
);
|
||||
|
||||
const onAppendTag = useCallback(
|
||||
(created: string[]) => {
|
||||
setData(
|
||||
uniq([...data, ...created]).filter(
|
||||
title => !tags.some(it => it.title?.trim() === title?.trim())
|
||||
)
|
||||
);
|
||||
},
|
||||
[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]);
|
||||
|
||||
const exclude = useMemo(
|
||||
() => [...(data || []), ...(tags || []).filter(el => el.title).map(({ title }) => title!)],
|
||||
[data, tags]
|
||||
);
|
||||
|
||||
return (
|
||||
<TagField {...props}>
|
||||
{catTags.map(tag => (
|
||||
<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}
|
||||
is_deletable={is_deletable}
|
||||
onDelete={onTagDelete}
|
||||
/>
|
||||
))}
|
||||
|
||||
{data.map(title => (
|
||||
<Tag key={title} tag={{ title }} is_editing />
|
||||
))}
|
||||
|
||||
{is_editable && (
|
||||
<TagInput
|
||||
onAppend={onAppendTag}
|
||||
onClearTag={onClearTag}
|
||||
onSubmit={onSubmit}
|
||||
exclude={exclude}
|
||||
/>
|
||||
)}
|
||||
</TagField>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue