1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 21:06:42 +07:00

clearing search

This commit is contained in:
Fedor Katurov 2020-04-18 18:46:51 +07:00
parent 9498c7b7a0
commit 47adbdf6f0
13 changed files with 215 additions and 195 deletions

View file

@ -1,11 +1,6 @@
import React, { FC } from 'react';
import * as styles from './styles.scss';
import { IFlowState } from '~/redux/flow/reducer';
import { getURL, getPrettyDate } from '~/utils/dom';
import { Link } from 'react-router-dom';
import { URLS, PRESETS } from '~/constants/urls';
import classNames from 'classnames';
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
import { FlowRecentItem } from '../FlowRecentItem';
interface IProps {
recent: IFlowState['recent'];
@ -15,36 +10,9 @@ interface IProps {
const FlowRecent: FC<IProps> = ({ recent, updated }) => {
return (
<>
{updated &&
updated.slice(0, 20).map(node => (
<Link key={node.id} className={styles.item} to={URLS.NODE_URL(node.id)}>
<div
className={classNames(styles.thumb, styles.new)}
style={{
backgroundImage: `url("${getURL({ url: node.thumbnail }, PRESETS.avatar)}")`,
}}
/>
{updated && updated.map(node => <FlowRecentItem node={node} key={node.id} has_new />)}
<div className={styles.info}>
<div className={styles.title}>{node.title}</div>
<div className={styles.comment}>{getPrettyDate(node.created_at)}</div>
</div>
</Link>
))}
{recent &&
recent.slice(0, 20).map(node => (
<Link key={node.id} className={styles.item} to={URLS.NODE_URL(node.id)}>
<div className={styles.thumb}>
<NodeRelatedItem item={node} />
</div>
<div className={styles.info}>
<div className={styles.title}>{node.title}</div>
<div className={styles.comment}>{getPrettyDate(node.created_at)}</div>
</div>
</Link>
))}
{recent && recent.map(node => <FlowRecentItem node={node} key={node.id} />)}
</>
);
};

View file

@ -1,56 +0,0 @@
.item {
display: flex;
align-items: center;
justify-content: center;
font: $font_12_regular;
border-radius: $radius;
flex-direction: row;
margin-bottom: $gap;
color: white;
text-decoration: none;
}
.thumb {
height: 48px;
margin-right: $gap;
background: 50% 50% no-repeat;
background-size: cover;
border-radius: $radius;
flex: 0 0 48px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.new {
&::after {
content: ' ';
width: 12px;
height: 12px;
border-radius: 100%;
background: $red;
box-shadow: $content_bg 0 0 0 5px;
position: absolute;
right: -2px;
bottom: -2px;
}
}
}
.info {
flex: 1;
min-width: 0;
}
.title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font: $font_16_semibold;
}
.comment {
font: $font_12_regular;
margin-top: 4px;
opacity: 0.5;
}

View file

@ -0,0 +1,30 @@
import React, { FC } from 'react';
import { INode } from '~/redux/types';
import styles from './styles.scss';
import { URLS } from '~/constants/urls';
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
import { getPrettyDate } from '~/utils/dom';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
interface IProps {
node: Partial<INode>;
has_new?: boolean;
}
const FlowRecentItem: FC<IProps> = ({ node, has_new }) => {
return (
<Link key={node.id} className={styles.item} to={URLS.NODE_URL(node.id)}>
<div className={classNames(styles.thumb, { [styles.new]: has_new })}>
<NodeRelatedItem item={node} />
</div>
<div className={styles.info}>
<div className={styles.title}>{node.title}</div>
<div className={styles.comment}>{getPrettyDate(node.created_at)}</div>
</div>
</Link>
);
};
export { FlowRecentItem };

View file

@ -0,0 +1,57 @@
.item {
display: flex;
align-items: center;
justify-content: center;
font: $font_12_regular;
border-radius: $radius;
flex-direction: row;
margin-bottom: $gap;
color: white;
text-decoration: none;
}
.thumb {
height: 48px;
margin-right: $gap;
background: 50% 50% no-repeat;
background-size: cover;
border-radius: $radius;
flex: 0 0 48px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.new {
&::after {
content: ' ';
width: 12px;
height: 12px;
border-radius: 100%;
background: $red;
box-shadow: $content_bg 0 0 0 5px;
position: absolute;
right: -2px;
bottom: -2px;
}
}
}
.info {
flex: 1;
min-width: 0;
}
.title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font: $font_16_semibold;
text-transform: capitalize;
}
.comment {
font: $font_12_regular;
margin-top: 4px;
opacity: 0.5;
}

View file

@ -1,11 +1,9 @@
import React, { FC, useCallback, MouseEvent } from 'react';
import React, { FC, useCallback } from 'react';
import styles from './styles.scss';
import { IFlowState } from '~/redux/flow/reducer';
import { LoaderCircle } from '~/components/input/LoaderCircle';
import { URLS, PRESETS } from '~/constants/urls';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { getURL, getPrettyDate } from '~/utils/dom';
import { FlowRecentItem } from '../FlowRecentItem';
import { Icon } from '~/components/input/Icon';
interface IProps {
search: IFlowState['search'];
@ -33,22 +31,19 @@ const FlowSearchResults: FC<IProps> = ({ search, onLoadMore }) => {
);
}
if (!search.results.length) {
return (
<div className={styles.loading}>
<Icon size={96} icon="search" />
<div className={styles.nothing}>Ничего не найдено</div>
</div>
);
}
return (
<div className={styles.wrap} onScroll={onScroll}>
{search.results.map(node => (
<Link key={node.id} className={styles.item} to={URLS.NODE_URL(node.id)}>
<div
className={classNames(styles.thumb)}
style={{
backgroundImage: `url("${getURL({ url: node.thumbnail }, PRESETS.avatar)}")`,
}}
/>
<div className={styles.info}>
<div className={styles.title}>{node.title}</div>
<div className={styles.comment}>{getPrettyDate(node.created_at)}</div>
</div>
</Link>
<FlowRecentItem node={node} key={node.id} />
))}
</div>
);

View file

@ -7,64 +7,20 @@
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex: 1;
opacity: 0.3;
fill: transparentize(white, 0.7);
stroke: none;
}
.item {
display: flex;
align-items: center;
justify-content: center;
font: $font_12_regular;
border-radius: $radius;
flex-direction: row;
margin-bottom: $gap;
.nothing {
color: white;
text-decoration: none;
}
.thumb {
height: 48px;
margin-right: $gap;
background: 50% 50% no-repeat;
background-size: cover;
border-radius: $radius;
flex: 0 0 48px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.new {
&::after {
content: ' ';
width: 12px;
height: 12px;
border-radius: 100%;
background: $red;
box-shadow: $content_bg 0 0 0 5px;
position: absolute;
right: -2px;
bottom: -2px;
}
}
}
.info {
flex: 1;
min-width: 0;
}
.title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font: $font_16_semibold;
text-transform: capitalize;
}
.comment {
font: $font_12_regular;
margin-top: 4px;
opacity: 0.5;
text-transform: uppercase;
font: $font_18_semibold;
font-weight: 900;
opacity: 0.3;
padding: 10px 20px;
text-align: center;
line-height: 1.5em;
}

View file

@ -1,4 +1,4 @@
import React, { FC, useCallback, FormEvent } from 'react';
import React, { FC, useCallback, FormEvent, useMemo, KeyboardEvent } from 'react';
import { IFlowState } from '~/redux/flow/reducer';
import { InputText } from '~/components/input/InputText';
import { FlowRecent } from '../FlowRecent';
@ -7,6 +7,7 @@ import classnames from 'classnames';
import * as styles from './styles.scss';
import * as FLOW_ACTIONS from '~/redux/flow/actions';
import { FlowSearchResults } from '../FlowSearchResults';
import { Icon } from '~/components/input/Icon';
interface IProps {
recent: IFlowState['recent'];
@ -25,10 +26,37 @@ const FlowStamp: FC<IProps> = ({ recent, updated, search, flowChangeSearch, onLo
event.preventDefault();
}, []);
const onClearSearch = useCallback(() => flowChangeSearch({ text: '' }), [flowChangeSearch]);
const onKeyUp = useCallback(
event => {
if (event.key !== 'Escape') return;
onClearSearch();
event.target.blur();
},
[onClearSearch]
);
const after = useMemo(
() =>
search.text ? (
<Icon icon="close" size={24} className={styles.close_icon} onClick={onClearSearch} />
) : (
<Icon icon="search" size={24} className={styles.search_icon} />
),
[search.text]
);
return (
<div className={styles.wrap}>
<form className={styles.search} onSubmit={onSearchSubmit}>
<InputText title="Поиск" value={search.text} handler={onSearchChange} />
<InputText
title="Поиск"
value={search.text}
handler={onSearchChange}
after={after}
onKeyUp={onKeyUp}
/>
</form>
<div className={styles.grid}>

View file

@ -13,6 +13,19 @@
flex: 1;
border-radius: $radius;
overflow: hidden;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
height: 60px;
width: 100%;
background: linear-gradient(transparentize($content_bg, 1), $content_bg 90%);
pointer-events: none;
touch-action: none;
}
@include outer_shadow();
}
@ -53,9 +66,28 @@
}
.search {
background: lighten($content_bg, 4%);
background: lighten($content_bg, 3%);
border-radius: $radius $radius 0 0;
padding: $gap;
@include outer_shadow();
}
.search_icon {
fill: white;
opacity: 0.1;
stroke: white;
stroke-width: 0.5;
}
.close_icon {
cursor: pointer;
stroke: white;
stroke-width: 0.5;
opacity: 0.5;
transition: opacity 0.25s;
&:hover {
opacity: 0.7;
}
}

View file

@ -16,6 +16,7 @@ const InputText: FC<IInputTextProps> = ({
value = '',
onRef,
is_loading,
after,
...props
}) => {
const [focused, setFocused] = useState(false);
@ -61,6 +62,7 @@ const InputText: FC<IInputTextProps> = ({
<div className={classNames(styles.success_icon, { active: status === 'success' })}>
<Icon icon="check" size={20} />
</div>
<div className={classNames(styles.error_icon, { active: status === 'error' || !!error })}>
<Icon icon="close" size={20} />
</div>
@ -83,6 +85,8 @@ const InputText: FC<IInputTextProps> = ({
<span>{error}</span>
</div>
)}
{!!after && <div className={styles.after}>{after}</div>}
</div>
);
};

View file

@ -80,16 +80,4 @@ $cols: $content_width / $cell;
justify-content: stretch;
overflow: hidden;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
height: 60px;
width: 100%;
background: linear-gradient(transparentize($content_bg, 1), $content_bg 90%);
pointer-events: none;
touch-action: none;
}
}

View file

@ -1,4 +1,4 @@
import { DetailedHTMLProps, InputHTMLAttributes } from 'react';
import { DetailedHTMLProps, InputHTMLAttributes, ReactElement } from 'react';
import { DIALOGS } from '~/redux/modal/constants';
import { ERRORS } from '~/constants/errors';
import { IUser } from './auth/types';
@ -30,6 +30,7 @@ export type IInputTextProps = DetailedHTMLProps<
mask?: string;
onRef?: (ref: any) => void;
is_loading?: boolean;
after?: ReactElement;
};
export type IIcon = string;

View file

@ -217,6 +217,11 @@ const Sprites: FC<{}> = () => (
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />
</g>
<g id="search">
<path fill="none" d="M0 0h24v24H0V0z" stroke="none" />
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</g>
<g id="youtube" stroke="none">
<path fill="none" d="M0 0h24v24H0V0z" />
<g transform="scale(0.1) translate(-30 -30)">

View file

@ -12,21 +12,6 @@
@include inner_shadow();
background: $input_bg_color;
&::before {
content: ' ';
background: linear-gradient(270deg, $input_bg_color $gap, transparentize($input_bg_color, 1));
position: absolute;
width: $gap * 2;
height: $input_height;
top: 1px;
right: 1px;
transform: translateX(0);
transition: transform 0.25s;
border-radius: 0 $input_radius $input_radius 0;
pointer-events: none;
touch-action: none;
}
:global(.react-datepicker-wrapper) {
flex: 1;
padding: 0 18px;
@ -90,6 +75,22 @@
flex: 1 0 0;
outline: none;
color: white;
position: relative;
&::before {
content: ' ';
background: linear-gradient(270deg, $input_bg_color $gap, transparentize($input_bg_color, 1));
position: absolute;
width: $gap * 2;
height: $input_height;
top: 1px;
right: 1px;
transform: translateX(0);
transition: transform 0.25s;
border-radius: 0 $input_radius $input_radius 0;
pointer-events: none;
touch-action: none;
}
}
&.required {
@ -374,3 +375,14 @@
border-radius: $input_radius;
}
}
.after {
width: 24px;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
top: -1px;
}