mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
fixed comments loader
This commit is contained in:
parent
0a1d2cbf99
commit
bb81bdae69
16 changed files with 80 additions and 127 deletions
|
@ -4,18 +4,17 @@ import Tippy from '@tippyjs/react';
|
|||
import classnames from 'classnames';
|
||||
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||
import { IIcon } from '~/types';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
|
||||
type IButtonProps = DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
size?: 'mini' | 'normal' | 'big' | 'giant' | 'micro' | 'small';
|
||||
color?: 'primary' | 'secondary' | 'outline' | 'link' | 'gray' | 'lab' | 'outline-white';
|
||||
color?: 'primary' | 'secondary' | 'outline' | 'link' | 'gray' | 'lab' | 'outline-white' | 'flat';
|
||||
iconLeft?: IIcon;
|
||||
iconRight?: IIcon;
|
||||
title?: string;
|
||||
|
@ -23,6 +22,7 @@ type IButtonProps = DetailedHTMLProps<
|
|||
iconOnly?: boolean;
|
||||
label?: string;
|
||||
round?: boolean;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
const Button: FC<IButtonProps> = memo(
|
||||
|
@ -40,6 +40,7 @@ const Button: FC<IButtonProps> = memo(
|
|||
label,
|
||||
ref,
|
||||
round,
|
||||
loading,
|
||||
...props
|
||||
}) => {
|
||||
const computedClassName = useMemo(
|
||||
|
@ -73,6 +74,9 @@ const Button: FC<IButtonProps> = memo(
|
|||
{iconLeft && <Icon icon={iconLeft} size={20} key={0} className={styles.icon_left} />}
|
||||
{!!title ? <span>{title}</span> : children}
|
||||
{iconRight && <Icon icon={iconRight} size={20} key={2} className={styles.icon_right} />}
|
||||
{
|
||||
loading && <div className={styles.loading}><LoaderCircle /></div>
|
||||
}
|
||||
</button>
|
||||
</Tippy>
|
||||
);
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
|
||||
&:global(.stretchy) {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:global(.disabled),
|
||||
|
@ -243,3 +244,21 @@
|
|||
.lab {
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
.flat {
|
||||
box-shadow: none;
|
||||
background: $comment_bg;
|
||||
color: darken(white, 20%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparentize($content_bg, 0.1);
|
||||
}
|
||||
|
|
18
src/components/input/LoadMoreButton/index.tsx
Normal file
18
src/components/input/LoadMoreButton/index.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface LoadMoreButtonProps {
|
||||
isLoading: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const LoadMoreButton: FC<LoadMoreButtonProps> = ({ isLoading, onClick }) => (
|
||||
<Button color="flat" onClick={onClick} stretchy className={styles.more} loading={isLoading}>
|
||||
Показать ещё комментарии
|
||||
</Button>
|
||||
);
|
||||
|
||||
export { LoadMoreButton }
|
5
src/components/input/LoadMoreButton/styles.module.scss
Normal file
5
src/components/input/LoadMoreButton/styles.module.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.more {
|
||||
width: 100%;
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -12,25 +12,5 @@
|
|||
.icon {
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.e0 {
|
||||
animation: spin infinite linear 2s;
|
||||
transform-origin: 50% 50%;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.e1 {
|
||||
animation: spin infinite reverse 2s -0.25s;
|
||||
transform-origin: 50% 50%;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.e2 {
|
||||
animation: spin infinite linear 2s -0.25s;
|
||||
transform-origin: 50% 50%;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.e {
|
||||
opacity: 0.1;
|
||||
animation: spin 0.75s infinite linear;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { NodeNoComments } from '~/components/node/NodeNoComments';
|
|||
import { isSSR } from '~/constants/ssr';
|
||||
import { NodeComments } from '~/containers/node/NodeComments';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import { CommentContextProvider, useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
||||
|
||||
|
@ -21,23 +21,10 @@ const BorisComments: FC<IProps> = () => {
|
|||
isLoading,
|
||||
comments,
|
||||
onSaveComment,
|
||||
onLoadMoreComments,
|
||||
onDeleteComment,
|
||||
onShowImageModal,
|
||||
hasMore,
|
||||
} = useCommentContext();
|
||||
const { node } = useNodeContext();
|
||||
|
||||
return (
|
||||
<CommentContextProvider
|
||||
onSaveComment={onSaveComment}
|
||||
comments={comments}
|
||||
hasMore={hasMore}
|
||||
onDeleteComment={onDeleteComment}
|
||||
onLoadMoreComments={onLoadMoreComments}
|
||||
onShowImageModal={onShowImageModal}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<Group>
|
||||
{(isUser || isSSR) && (
|
||||
<NodeCommentFormSSR user={user} nodeId={node.id} saveComment={onSaveComment} />
|
||||
|
@ -51,7 +38,6 @@ const BorisComments: FC<IProps> = () => {
|
|||
|
||||
<Footer />
|
||||
</Group>
|
||||
</CommentContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { FC, memo, useMemo } from 'react';
|
||||
|
||||
import { Comment } from '~/components/comment/Comment';
|
||||
import { LoadMoreButton } from '~/components/input/LoadMoreButton';
|
||||
import { useGrouppedComments } from '~/hooks/node/useGrouppedComments';
|
||||
import { ICommentGroup } from '~/types';
|
||||
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||
|
@ -21,6 +22,8 @@ const NodeComments: FC<IProps> = memo(({ order }) => {
|
|||
const {
|
||||
comments,
|
||||
hasMore,
|
||||
isLoading,
|
||||
isLoadingMore,
|
||||
lastSeenCurrent,
|
||||
onLoadMoreComments,
|
||||
onDeleteComment,
|
||||
|
@ -36,12 +39,10 @@ const NodeComments: FC<IProps> = memo(({ order }) => {
|
|||
|
||||
const more = useMemo(
|
||||
() =>
|
||||
hasMore && (
|
||||
<div className={styles.more} onClick={onLoadMoreComments}>
|
||||
Показать ещё комментарии
|
||||
</div>
|
||||
),
|
||||
[hasMore, onLoadMoreComments]
|
||||
hasMore && <div className={styles.more}>
|
||||
<LoadMoreButton isLoading={isLoadingMore} onClick={onLoadMoreComments} />
|
||||
</div>,
|
||||
[hasMore, onLoadMoreComments, isLoadingMore]
|
||||
);
|
||||
|
||||
if (!node?.id) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "../../../styles/variables";
|
||||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
& > div {
|
||||
|
@ -11,40 +11,5 @@
|
|||
}
|
||||
|
||||
.more {
|
||||
padding: $gap;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius;
|
||||
text-transform: uppercase;
|
||||
color: darken(white, 60%);
|
||||
font: $font_14_medium;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s;
|
||||
user-select: none;
|
||||
background: url('../../../sprites/stripes.svg');
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: $wisegreen;
|
||||
background-color: darken($wisegreen, 12%);
|
||||
|
||||
.bar {
|
||||
background: $wisegreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: darken(white, 60%);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
width: 50%;
|
||||
transition: width 0.25s;
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
|
|
|
@ -45,9 +45,10 @@ export const useGetComments = (nodeId: number, fallbackData?: IComment[]) => {
|
|||
);
|
||||
|
||||
const comments = useMemo(() => flatten(data || []), [data]);
|
||||
const hasMore = (data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY;
|
||||
const hasMore = (data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY ||
|
||||
(!!data?.length && data?.length > 0 && isValidating);
|
||||
|
||||
const onLoadMoreComments = useCallback(() => setSize(size + 1), [setSize, size]);
|
||||
|
||||
return { comments, hasMore, onLoadMoreComments, isLoading: !data && isValidating, mutate, data };
|
||||
return { comments, hasMore, onLoadMoreComments, isLoading: !data && isValidating, mutate, data, isLoadingMore: !!data?.length && isValidating };
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { IComment } from '~/types';
|
|||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
||||
export const useNodeComments = (nodeId: number, fallbackData?: IComment[]) => {
|
||||
const { comments, isLoading, onLoadMoreComments, hasMore, data, mutate } = useGetComments(
|
||||
const { comments, isLoading, onLoadMoreComments, hasMore, data, mutate, isLoadingMore } = useGetComments(
|
||||
nodeId,
|
||||
fallbackData
|
||||
);
|
||||
|
@ -62,5 +62,5 @@ export const useNodeComments = (nodeId: number, fallbackData?: IComment[]) => {
|
|||
[data, mutate, nodeId]
|
||||
);
|
||||
|
||||
return { onLoadMoreComments, onDelete, comments, hasMore, isLoading, onEdit };
|
||||
return { onLoadMoreComments, onDelete, comments, hasMore, isLoading, onEdit, isLoadingMore };
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import { SearchProvider } from '~/utils/providers/SearchProvider';
|
|||
import { ToastProvider } from '~/utils/providers/ToastProvider';
|
||||
|
||||
import '~/styles/main.scss';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
|
||||
const mobxStore = getMOBXStore();
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ const BorisPage: VFC = observer(() => {
|
|||
comments,
|
||||
hasMore,
|
||||
isLoading: isLoadingComments,
|
||||
isLoadingMore,
|
||||
} = useNodeComments(696);
|
||||
const { title, setIsBetaTester, isTester, stats, isLoadingStats } = useBoris(comments);
|
||||
|
||||
|
@ -33,6 +34,7 @@ const BorisPage: VFC = observer(() => {
|
|||
comments={comments}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoadingComments}
|
||||
isLoadingMore={isLoadingMore}
|
||||
onShowImageModal={onShowImageModal}
|
||||
onLoadMoreComments={onLoadMoreComments}
|
||||
onDeleteComment={onDeleteComment}
|
||||
|
|
|
@ -99,6 +99,7 @@ const NodePage: FC<Props> = observer(props => {
|
|||
comments,
|
||||
hasMore,
|
||||
isLoading: isLoadingComments,
|
||||
isLoadingMore: isLoadingMoreComments,
|
||||
} = useNodeComments(parseInt(id, 10), props.comments);
|
||||
|
||||
const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags(
|
||||
|
@ -122,6 +123,7 @@ const NodePage: FC<Props> = observer(props => {
|
|||
hasMore={hasMore}
|
||||
lastSeenCurrent={lastSeen}
|
||||
isLoading={isLoadingComments}
|
||||
isLoadingMore={isLoadingMoreComments}
|
||||
onShowImageModal={onShowImageModal}
|
||||
onLoadMoreComments={onLoadMoreComments}
|
||||
onDeleteComment={onDeleteComment}
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface CommentProviderProps {
|
|||
hasMore: boolean;
|
||||
lastSeenCurrent?: string | null;
|
||||
isLoading: boolean;
|
||||
isLoadingMore: boolean;
|
||||
onShowImageModal: (images: IFile[], index: number) => void;
|
||||
onLoadMoreComments: () => void;
|
||||
onSaveComment: (comment: IComment) => Promise<unknown>;
|
||||
|
@ -19,6 +20,7 @@ const CommentContext = createContext<CommentProviderProps>({
|
|||
hasMore: false,
|
||||
lastSeenCurrent: null,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
onSaveComment: async () => {},
|
||||
onShowImageModal: () => {},
|
||||
onLoadMoreComments: () => {},
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue