mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
refactored lab
This commit is contained in:
parent
d0e99adc9f
commit
b551fc44ea
41 changed files with 80 additions and 79 deletions
|
@ -1,13 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { NodeAudioBlock } from '~/components/node/NodeAudioBlock';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
|
||||
const LabAudio: FC<NodeComponentProps> = ({ node, isLoading }) => (
|
||||
<Placeholder active={isLoading} width="100%" height={100}>
|
||||
<NodeAudioBlock node={node} isLoading={isLoading} />
|
||||
</Placeholder>
|
||||
);
|
||||
|
||||
export { LabAudio };
|
|
@ -1,27 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { LabSquare } from '~/components/lab/LabSquare';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const LabBanner: FC<IProps> = () => (
|
||||
<LabSquare className={styles.wrap}>
|
||||
<Group>
|
||||
<div className={styles.title}>Лаборатория!</div>
|
||||
|
||||
<Group className={styles.content}>
|
||||
<p>
|
||||
<strong>
|
||||
Всё, что происходит здесь — всего лишь эксперимент, о котором
|
||||
не узнает никто за пределами Убежища.
|
||||
</strong>
|
||||
</p>
|
||||
</Group>
|
||||
</Group>
|
||||
</LabSquare>
|
||||
);
|
||||
|
||||
export { LabBanner };
|
|
@ -1,23 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.wrap {
|
||||
@include outer_shadow;
|
||||
|
||||
background: url('/images/boris_lab.svg') 50% 50% no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: stretch;
|
||||
border-radius: $radius;
|
||||
padding: $gap;
|
||||
}
|
||||
|
||||
.title {
|
||||
font: $font_24_bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
font: $font_14_regular;
|
||||
line-height: 19px;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import { FC, useCallback } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Grid } from '~/components/common/Grid';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/common/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
import { INode } from '~/types';
|
||||
import { getPrettyDate } from '~/utils/dom';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type Props = {
|
||||
node: INode;
|
||||
isLoading?: boolean;
|
||||
hasNewComments: boolean;
|
||||
commentCount: number;
|
||||
};
|
||||
|
||||
const LabBottomPanel: FC<Props> = ({
|
||||
node,
|
||||
hasNewComments,
|
||||
commentCount,
|
||||
isLoading,
|
||||
}) => {
|
||||
const { push } = useNavigation();
|
||||
const onClick = useCallback(
|
||||
() => push(URLS.NODE_URL(node.id)),
|
||||
[push, node.id],
|
||||
);
|
||||
|
||||
return (
|
||||
<Group horizontal className={styles.wrap} onClick={onClick}>
|
||||
<div className={styles.timestamp}>
|
||||
<Placeholder active={isLoading}>
|
||||
{getPrettyDate(node.created_at)}
|
||||
</Placeholder>
|
||||
</div>
|
||||
|
||||
<Filler />
|
||||
|
||||
<Placeholder active={isLoading} width="48px" height={24}>
|
||||
{commentCount > 0 && (
|
||||
<Grid
|
||||
horizontal
|
||||
className={classNames(styles.comments, {
|
||||
[styles.active]: hasNewComments,
|
||||
})}
|
||||
>
|
||||
<Icon icon={hasNewComments ? 'comment_new' : 'comment'} size={24} />
|
||||
<span>{commentCount}</span>
|
||||
</Grid>
|
||||
)}
|
||||
</Placeholder>
|
||||
|
||||
<Placeholder active={isLoading} width="48px" height={24}>
|
||||
{!!node.like_count && node.like_count > 0 && (
|
||||
<Grid horizontal className={classNames(styles.like)}>
|
||||
<Icon icon={node.is_liked ? 'heart_full' : 'heart'} size={24} />
|
||||
<span>{node.like_count}</span>
|
||||
</Grid>
|
||||
)}
|
||||
</Placeholder>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabBottomPanel };
|
|
@ -1,31 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
.wrap {
|
||||
padding: $gap $lab_gap $lab_gap;
|
||||
|
||||
@include tablet {
|
||||
padding: $gap $lab_gap_mobile $lab_gap_mobile;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font: $font_12_regular;
|
||||
color: $gray_50;
|
||||
}
|
||||
|
||||
.comments,
|
||||
.like {
|
||||
flex: 0;
|
||||
font: $font_16_semibold;
|
||||
color: $gray_50;
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
column-gap: $gap !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: $gap;
|
||||
|
||||
&.active {
|
||||
color: $color_danger;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { Paragraph } from '~/components/placeholders/Paragraph';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
import { formatText } from '~/utils/dom';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const LabDescription: FC<NodeComponentProps> = ({ node, isLoading }) => {
|
||||
const onClick = useGotoNode(node.id);
|
||||
|
||||
if (!node.description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<div className={styles.wrap}>
|
||||
<Paragraph />
|
||||
</div>
|
||||
) : (
|
||||
<Markdown className={styles.wrap} onClick={onClick}>
|
||||
{formatText(node.description)}
|
||||
</Markdown>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabDescription };
|
|
@ -1,14 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.wrap {
|
||||
padding: $lab_gap * 0.5 $lab_gap 0;
|
||||
line-height: 1.3em;
|
||||
|
||||
@include tablet {
|
||||
@include clamp(10, 1.3*16px);
|
||||
line-height: 1.3em;
|
||||
font: $font_16_regular;
|
||||
padding: 0 $lab_gap_mobile;
|
||||
margin: $lab_gap_mobile * 0.5 0;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { useRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const LabFactoryBanner = () => {
|
||||
const masked = useRef(Math.random() <= 0.5).current;
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.banner, { [styles.masked]: masked })}>
|
||||
<Group>
|
||||
<div className={styles.title}>Лаборатория!</div>
|
||||
|
||||
<Group className={styles.content}>
|
||||
<p>
|
||||
<strong>
|
||||
Всё, что происходит здесь — всего лишь эксперимент, о
|
||||
котором не узнает никто за пределами Убежища.
|
||||
</strong>
|
||||
</p>
|
||||
</Group>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export { LabFactoryBanner };
|
|
@ -1,26 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.banner {
|
||||
@include outer_shadow;
|
||||
|
||||
display: flex;
|
||||
aspect-ratio: 0.7;
|
||||
background: url('/images/peoples_lab.svg') 50% 100% / cover;
|
||||
padding: $gap;
|
||||
border-radius: $radius;
|
||||
|
||||
&.masked {
|
||||
background-image: url('/images/peoples_lab_masked.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font: $font_24_bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
font: $font_14_medium;
|
||||
line-height: 19px;
|
||||
opacity: 0.7;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { SearchInput } from '~/components/input/SearchInput';
|
||||
import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
|
||||
import { LabNodesSort } from '~/types/lab';
|
||||
import { useLabContext } from '~/utils/context/LabContextProvider';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const LabHead: FC<IProps> = ({ isLoading }) => {
|
||||
const { sort, setSort, search, setSearch } = useLabContext();
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<HorizontalMenu>
|
||||
<HorizontalMenu.Item
|
||||
color="green"
|
||||
icon="recent"
|
||||
active={sort === LabNodesSort.New}
|
||||
isLoading={isLoading}
|
||||
onClick={() => setSort(LabNodesSort.New)}
|
||||
>
|
||||
Свежие
|
||||
</HorizontalMenu.Item>
|
||||
|
||||
<HorizontalMenu.Item
|
||||
color="orange"
|
||||
icon="hot"
|
||||
active={sort === LabNodesSort.Hot}
|
||||
isLoading={isLoading}
|
||||
onClick={() => setSort(LabNodesSort.Hot)}
|
||||
>
|
||||
Популярные
|
||||
</HorizontalMenu.Item>
|
||||
|
||||
<HorizontalMenu.Item
|
||||
color="yellow"
|
||||
icon="star_full"
|
||||
isLoading={isLoading}
|
||||
active={sort === LabNodesSort.Heroic}
|
||||
onClick={() => setSort(LabNodesSort.Heroic)}
|
||||
>
|
||||
Важные
|
||||
</HorizontalMenu.Item>
|
||||
</HorizontalMenu>
|
||||
|
||||
<Filler />
|
||||
|
||||
<div className={styles.search}>
|
||||
<SearchInput value={search} handler={setSearch} placeholder="Поиск" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabHead };
|
|
@ -1,17 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.wrap {
|
||||
border-radius: $radius;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@include tablet {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
@include tablet {
|
||||
margin-top: $gap;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { FC, useCallback } from 'react';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/common/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
import { INode } from '~/types';
|
||||
import { getPrettyDate } from '~/utils/dom';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
node?: Partial<INode>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const LabHero: FC<IProps> = ({ node, isLoading }) => {
|
||||
const { push } = useNavigation();
|
||||
const onClick = useCallback(() => {
|
||||
push(URLS.NODE_URL(node?.id));
|
||||
}, [push, node]);
|
||||
|
||||
if (!node || isLoading) {
|
||||
return (
|
||||
<Group horizontal className={styles.wrap1}>
|
||||
<div className={styles.star}>
|
||||
<Icon icon="star_full" size={32} />
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<Placeholder height={20} />
|
||||
<Placeholder height={12} width="100px" />
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group horizontal className={styles.wrap} onClick={onClick}>
|
||||
<div className={styles.star}>
|
||||
<Icon icon="star_full" size={32} />
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{node.title}</div>
|
||||
<div className={styles.description}>
|
||||
{getPrettyDate(node.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabHero };
|
|
@ -1,33 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
.wrap {
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.star {
|
||||
fill: $gray_75;
|
||||
flex: 0 0 32px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font: $font_18_semibold;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 22px;
|
||||
word-break: break-all;
|
||||
color: $gray_50;
|
||||
|
||||
@include clamp(2, 22px);
|
||||
}
|
||||
|
||||
.description {
|
||||
font: $font_10_regular;
|
||||
color: $gray_50;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: $gap * 0.5 0;
|
||||
text-decoration: none;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { LabHero } from '~/components/lab/LabHero';
|
||||
import styles from '~/containers/lab/LabStats/styles.module.scss';
|
||||
import { INode } from '~/types';
|
||||
|
||||
interface IProps {
|
||||
nodes: Partial<INode>[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const empty = [...new Array(5)].map((_, i) => i);
|
||||
|
||||
const LabHeroes: FC<IProps> = ({ nodes, isLoading }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Group className={styles.heroes}>
|
||||
{empty.map((i) => (
|
||||
<LabHero isLoading key={i} />
|
||||
))}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group className={styles.heroes}>
|
||||
{nodes.slice(0, 10).map((node) => (
|
||||
<LabHero node={node} key={node?.id} />
|
||||
))}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabHeroes };
|
|
@ -1,44 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import Image from 'next/future/image';
|
||||
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { imagePresets } from '~/constants/urls';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
import { useNodeImages } from '~/hooks/node/useNodeImages';
|
||||
import { normalizeBrightColor } from '~/utils/color';
|
||||
import { getURL } from '~/utils/dom';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps extends NodeComponentProps {}
|
||||
|
||||
const LabImage: FC<IProps> = ({ node, isLoading }) => {
|
||||
const images = useNodeImages(node);
|
||||
const onClick = useGotoNode(node.id);
|
||||
|
||||
if (!images?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const file = images[0];
|
||||
|
||||
return (
|
||||
<Placeholder active={isLoading} width="100%" height={400}>
|
||||
<div className={styles.wrapper}>
|
||||
<Image
|
||||
src={getURL(file, imagePresets[600])}
|
||||
width={file.metadata?.width}
|
||||
height={file.metadata?.height}
|
||||
onClick={onClick}
|
||||
alt=""
|
||||
className={styles.image}
|
||||
color={normalizeBrightColor(file?.metadata?.dominant_color)}
|
||||
/>
|
||||
</div>
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabImage };
|
|
@ -1,61 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
border-radius: $radius $radius 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
:global(.swiper-container) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.swiper-button-next),
|
||||
:global(.swiper-button-prev) {
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
|
||||
&::after {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font: $font_32_bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
opacity: 1;
|
||||
filter: brightness(50%) saturate(0.5);
|
||||
transition: opacity 0.5s, filter 0.5s, transform 0.5s;
|
||||
|
||||
&:global(.swiper-slide-active) {
|
||||
opacity: 1;
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
@include tablet {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
max-height: calc(100vh - 70px - 70px);
|
||||
max-width: 100%;
|
||||
transition: box-shadow 1s;
|
||||
max-inline-size: 100%;
|
||||
block-size: auto;
|
||||
|
||||
@include tablet {
|
||||
padding-bottom: 0;
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface Props extends NodeComponentProps {}
|
||||
|
||||
const LabLine: FC<Props> = ({ node: { title } }) => {
|
||||
const background = useColorGradientFromString(title, 5, 3, 270);
|
||||
|
||||
return <div className={styles.line} style={{ background }} />;
|
||||
};
|
||||
|
||||
export { LabLine };
|
|
@ -1,7 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.line {
|
||||
height: 4px;
|
||||
border-radius: $radius $radius 0 0;
|
||||
width: 100%;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { VFC } from 'react';
|
||||
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface LabNoResultsProps {
|
||||
resetSearch: () => void;
|
||||
}
|
||||
|
||||
const LabNoResults: VFC<LabNoResultsProps> = ({ resetSearch }) => (
|
||||
<Card className={styles.wrap}>
|
||||
<div className={styles.title}> Здесь ничего нет</div>
|
||||
<Button onClick={resetSearch}>Сбросить поиск</Button>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export { LabNoResults };
|
|
@ -1,16 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
padding: $gap * 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 33vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
font: $font_20_semibold;
|
||||
margin-bottom: $gap * 2;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import { FC, useMemo } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { isAfter, parseISO } from 'date-fns';
|
||||
|
||||
import { LabBottomPanel } from '~/components/lab/LabBottomPanel';
|
||||
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
|
||||
import { useNodeBlocks } from '~/hooks/node/useNodeBlocks';
|
||||
import { INode } from '~/types';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
node: INode;
|
||||
lastSeen: string | null | undefined;
|
||||
isLoading?: boolean;
|
||||
commentCount: number;
|
||||
}
|
||||
|
||||
const LabNode: FC<IProps> = ({ node, isLoading, lastSeen, commentCount }) => {
|
||||
const { lab } = useNodeBlocks(node, !!isLoading);
|
||||
|
||||
const hasNewComments = useMemo(
|
||||
() =>
|
||||
!!node.commented_at &&
|
||||
!!lastSeen &&
|
||||
isAfter(parseISO(node.commented_at), parseISO(lastSeen)),
|
||||
[node.commented_at, lastSeen],
|
||||
);
|
||||
|
||||
const background = useColorGradientFromString(node.title, 3, 2);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.wrap)} style={{ background }}>
|
||||
{lab}
|
||||
<LabBottomPanel
|
||||
node={node}
|
||||
isLoading={isLoading}
|
||||
hasNewComments={hasNewComments}
|
||||
commentCount={commentCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabNode };
|
|
@ -1,12 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
.wrap {
|
||||
@include outer_shadow;
|
||||
|
||||
position: relative;
|
||||
background-color: $content_bg;
|
||||
cursor: pointer;
|
||||
|
||||
min-width: 0;
|
||||
border-radius: $radius;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import Tippy from '@tippyjs/react';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/common/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const LabNodeTitle: FC<NodeComponentProps> = ({ node, isLoading }) => {
|
||||
const onClick = useGotoNode(node.id);
|
||||
|
||||
if (!node.title) return null;
|
||||
|
||||
return (
|
||||
<Group horizontal className={styles.wrap} onClick={onClick}>
|
||||
<div className={styles.title}>
|
||||
<Placeholder active={isLoading}>{node.title || '...'}</Placeholder>
|
||||
</div>
|
||||
|
||||
{(node.is_heroic || isLoading) && (
|
||||
<Placeholder active={isLoading} width="24px" height={24}>
|
||||
<Tippy content="Важный пост">
|
||||
<div className={styles.star}>
|
||||
<Icon icon="star_full" size={24} />
|
||||
</div>
|
||||
</Tippy>
|
||||
</Placeholder>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabNodeTitle };
|
|
@ -1,35 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
.wrap {
|
||||
padding: $lab_gap - $gap $lab_gap 0;
|
||||
|
||||
@include tablet {
|
||||
padding: $gap $lab_gap_mobile 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
font: $font_24_semibold;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
|
||||
@include clamp(2, 1.2em);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@include tablet {
|
||||
padding-bottom: 0;
|
||||
font: $font_20_semibold;
|
||||
}
|
||||
}
|
||||
|
||||
.star {
|
||||
fill: $gray_50;
|
||||
flex: 0 0 24px;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const LabPad: FC<NodeComponentProps> = ({ node }) => {
|
||||
const onClick = useGotoNode(node.id);
|
||||
return <div className={styles.pad} onClick={onClick} />;
|
||||
};
|
||||
|
||||
export { LabPad };
|
|
@ -1,5 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.pad {
|
||||
height: $gap;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DivProps } from '~/utils/types';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps extends DivProps {}
|
||||
|
||||
const LabSquare: FC<IProps> = ({ children, ...rest }) => (
|
||||
<div className={styles.square}>
|
||||
<div {...rest} className={classNames(styles.content, rest.className)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { LabSquare };
|
|
@ -1,12 +0,0 @@
|
|||
.square {
|
||||
position: relative;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { Tag } from '~/components/tags/Tag';
|
||||
import { ITag } from '~/types';
|
||||
|
||||
import styles from './/styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
tags: ITag[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const LabTags: FC<IProps> = ({ tags, isLoading }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={styles.tags}>
|
||||
<Placeholder height={20} width="100px" />
|
||||
<Placeholder height={20} width="64px" />
|
||||
<Placeholder height={20} width="100%" />
|
||||
<Placeholder height={20} width="100px" />
|
||||
<Placeholder height={20} width="100px" />
|
||||
<Placeholder height={20} width="64px" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.tags}>
|
||||
{tags.slice(0, 10).map((tag) => (
|
||||
<Tag tag={tag} key={tag.ID} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabTags };
|
|
@ -1,10 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > * {
|
||||
margin: $gap * 0.5;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { FC, useMemo } from 'react';
|
||||
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { Paragraph } from '~/components/placeholders/Paragraph';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
import { formatTextParagraphs } from '~/utils/dom';
|
||||
import { path } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const LabText: FC<NodeComponentProps> = ({ node, isLoading }) => {
|
||||
const content = useMemo(
|
||||
() => formatTextParagraphs(path(['blocks', 0, 'text'], node) || ''),
|
||||
[node],
|
||||
);
|
||||
|
||||
const onClick = useGotoNode(node.id);
|
||||
|
||||
return isLoading ? (
|
||||
<div className={styles.wrap}>
|
||||
<Paragraph lines={5} />
|
||||
</div>
|
||||
) : (
|
||||
<Markdown className={styles.wrap} onClick={onClick}>
|
||||
{content}
|
||||
</Markdown>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabText };
|
|
@ -1,13 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
.wrap {
|
||||
padding: 0 $lab_gap;
|
||||
line-height: 1.3em;
|
||||
|
||||
@include tablet {
|
||||
@include clamp(20, 1.3 * 16px);
|
||||
|
||||
padding: 0 $lab_gap_mobile;
|
||||
font: $font_16_regular;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { NodeVideoBlock } from '~/components/node/NodeVideoBlock';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
|
||||
const LabVideo: FC<NodeComponentProps> = ({ node, isLoading }) => (
|
||||
<Placeholder active={isLoading} width="100%" height={400}>
|
||||
<NodeVideoBlock node={node} isLoading={isLoading} />
|
||||
</Placeholder>
|
||||
);
|
||||
|
||||
export { LabVideo };
|
Loading…
Add table
Add a link
Reference in a new issue