1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-28 22:26:41 +07:00

Compare commits

..

No commits in common. "9e79cba7bffb1cf340c871c77613e63f56e9302a" and "f0606a894a3291b3beb399ba4a14e9c973b04b24" have entirely different histories.

21 changed files with 243 additions and 250 deletions

View file

@ -27,8 +27,8 @@
&.new {
&::after {
content: ' ';
width: 8px;
height: 8px;
width: 12px;
height: 12px;
border-radius: 100%;
background: $color_danger;
box-shadow: $content_bg 0 0 0 5px;

View file

@ -13,11 +13,9 @@ interface Props extends DivProps {
const SubTitle: FC<Props> = ({ isLoading, children, ...rest }) => (
<div {...rest} className={classNames(styles.title, rest.className)}>
<span className={styles.name}>
<Placeholder active={isLoading} loading>
{children}
</Placeholder>
</span>
<Placeholder active={isLoading} loading>
{children}
</Placeholder>
</div>
);

View file

@ -1,25 +1,7 @@
@import 'src/styles/variables.scss';
@import "src/styles/variables.scss";
.title {
font: $font_12_semibold;
text-transform: uppercase;
display: flex;
flex-direction: row;
align-items: center;
gap: $gap / 2;
color: var(--gray_75);
a {
text-decoration: none;
color: inherit;
}
&::after {
content: ' ';
display: flex;
height: 2px;
background-color: var(--gray_90);
flex: 1;
border-radius: 2px;
}
opacity: 0.3;
}

View file

@ -29,6 +29,11 @@
.title {
padding-left: 5px;
a {
text-decoration: none;
color: inherit;
}
}
.text {

View file

@ -5,5 +5,3 @@ export const isTablet = () => {
return window.innerWidth < 599;
};
export const headerHeight = 64; // px

View file

@ -23,7 +23,6 @@ interface Props {
const FlowCellMenu: FC<Props> = ({
onClose,
currentView,
hasDescription,
toggleViewDescription,
descriptionEnabled,
@ -60,7 +59,7 @@ const FlowCellMenu: FC<Props> = ({
/>
</div>
{hasDescription && currentView !== 'single' && (
{hasDescription && (
<Group
className={styles.description}
horizontal

View file

@ -1,7 +1,6 @@
import { FC, ReactElement } from 'react';
import classNames from 'classnames';
import { transparentize, darken, desaturate, getLuminance } from 'color2k';
import { Markdown } from '~/components/common/Markdown';
import { formatText } from '~/utils/dom';
@ -12,29 +11,13 @@ import styles from './styles.module.scss';
interface Props extends DivProps {
children: string;
heading: string | ReactElement;
color?: string;
}
const FlowCellText: FC<Props> = ({ children, heading, color, ...rest }) => {
const colorIsBright = !!color && getLuminance(color) > 0.4;
const textColor = colorIsBright
? desaturate(darken(color, 0.5), 0.1)
: undefined;
return (
<div
{...rest}
className={classNames(styles.text, rest.className)}
style={{
backgroundColor: color && transparentize(color, 0.5),
color: textColor,
}}
>
{heading && <div className={styles.heading}>{heading}</div>}
<Markdown className={styles.description}>{formatText(children)}</Markdown>
</div>
);
};
const FlowCellText: FC<Props> = ({ children, heading, ...rest }) => (
<div {...rest} className={classNames(styles.text, rest.className)}>
{heading && <div className={styles.heading}>{heading}</div>}
<Markdown className={styles.description}>{formatText(children)}</Markdown>
</div>
);
export { FlowCellText };

View file

@ -1,24 +1,10 @@
@import 'src/styles/variables';
@import "src/styles/variables";
.text {
@include blur;
padding: $gap;
line-height: 1.3em;
display: flex;
flex-direction: column;
min-height: 0;
}
.description {
mask-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 1) 50%,
rgba(0, 0, 0, 0) 95%
);
flex: 1;
overflow: hidden;
}
.heading {
margin-bottom: 0.25em;
margin-bottom: 0.4em;
}

View file

@ -1,4 +1,4 @@
import { FC, useCallback, useMemo } from 'react';
import { FC, useMemo } from 'react';
import classNames from 'classnames';
@ -9,8 +9,6 @@ import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { useFlowCellControls } from '~/hooks/flow/useFlowCellControls';
import { FlowDisplay, INode } from '~/types';
import { isFullyVisible } from '../../../../../utils/dom';
import { CellShade } from './components/CellShade';
import { FlowCellImage } from './components/FlowCellImage';
import { FlowCellMenu } from './components/FlowCellMenu';
@ -27,7 +25,7 @@ interface Props {
text?: string;
flow: FlowDisplay;
canEdit?: boolean;
onChange: (id: INode['id'], flow: FlowDisplay) => void;
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
}
const FlowCell: FC<Props> = ({
@ -39,7 +37,7 @@ const FlowCell: FC<Props> = ({
text,
title,
canEdit = false,
onChange,
onChangeCellView,
}) => {
const { isTablet } = useWindowSize();
@ -47,30 +45,6 @@ const FlowCell: FC<Props> = ({
((!!flow.display && flow.display !== 'single') || !image) &&
flow.show_description &&
!!text;
const {
isActive: isMenuActive,
activate,
ref,
deactivate,
} = useClickOutsideFocus();
const onChangeWithScroll = useCallback<typeof onChange>(
(...args) => {
onChange(...args);
setTimeout(() => {
if (!isFullyVisible(ref.current)) {
ref.current?.scrollIntoView({
behavior: 'auto',
block: 'center',
});
}
}, 0);
},
[onChange, ref],
);
const {
hasDescription,
setViewHorizontal,
@ -78,7 +52,13 @@ const FlowCell: FC<Props> = ({
setViewQuadro,
setViewSingle,
toggleViewDescription,
} = useFlowCellControls(id, text, flow, onChangeWithScroll);
} = useFlowCellControls(id, text, flow, onChangeCellView);
const {
isActive: isMenuActive,
activate,
ref,
deactivate,
} = useClickOutsideFocus();
const shadeSize = useMemo(() => {
const min = isTablet ? 10 : 15;
@ -131,9 +111,8 @@ const FlowCell: FC<Props> = ({
<FlowCellText
className={styles.text}
heading={<h4 className={styles.title}>{title}</h4>}
color={color}
>
{text}
{text!}
</FlowCellText>
)}
@ -145,7 +124,7 @@ const FlowCell: FC<Props> = ({
/>
)}
{!!title && !withText && (
{!!title && (
<CellShade
color={color}
className={styles.shade}

View file

@ -1,7 +1,5 @@
@import 'src/styles/variables';
$compact_size: 200px;
.cell {
@include inner_shadow;
@ -11,7 +9,6 @@ $compact_size: 200px;
width: 100%;
height: 100%;
background: $content_bg;
container: cell / inline-size;
}
.thumb {
@ -36,17 +33,20 @@ $compact_size: 200px;
.text {
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
overflow: hidden;
border-radius: $radius;
max-height: calc(100% - 10px);
max-width: calc(100% - 10px);
box-sizing: border-box;
inset: 50% 0 0 0;
padding: $gap $gap * 1.5 0 $gap * 1.5;
font: $font_14_medium;
line-height: 1.25em;
font: $font_16_regular;
@container (max-width: $compact_size) {
padding: $gap / 2 $gap 0 $gap;
@include tablet {
font: $font_14_regular;
left: 5px;
bottom: 5px;
}
& :global(.grey) {
@ -54,34 +54,14 @@ $compact_size: 200px;
opacity: 0.5;
}
@container (max-width: #{$compact_size}) {
padding: $gap / 2 $gap 0 $gap;
}
.horizontal &,
.quadro & {
@container (max-width: #{$compact_size * 2}) {
padding: $gap / 2 $gap 0 $gap;
}
}
.quadro &,
.horizontal & {
inset: 0 calc(50% + $gap / 2) 0 0;
border-radius: $radius 0 0 $radius;
max-width: calc(50% - 15px);
}
.quadro &,
.vertical & {
inset: calc(50% + $gap / 2) 0 0 0;
border-radius: 0 0 $radius $radius;
}
.quadro & {
inset: calc(50% + $gap / 2) calc(50% + $gap / 2) 0 0;
border-radius: 0 $radius 0 $radius;
}
.title {
margin-bottom: 0.1em;
max-height: calc(50% - 15px);
}
}
@ -96,21 +76,11 @@ $compact_size: 200px;
.title {
font: $font_cell_title;
line-height: 1.2em;
text-transform: uppercase;
word-break: break-word;
color: inherit;
margin-bottom: -0.125em;
@container (max-width: #{$compact_size}) {
font: $font_cell_title_compact;
}
.horizontal &,
.quadro & {
@container (max-width: #{$compact_size * 2}) {
font: $font_cell_title_compact;
}
@include tablet {
font: $font_18_semibold;
}
}
@ -137,7 +107,12 @@ $compact_size: 200px;
}
.display_modal {
@include appear;
position: absolute;
inset: 0;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 11;
}

View file

@ -46,7 +46,7 @@ export const FlowGrid: FC<Props> = observer(
text={node.description}
title={node.title}
canEdit={fetched && isUser && canEditNode(node, user)}
onChange={onChangeCellView}
onChangeCellView={onChangeCellView}
/>
</div>
))}

View file

@ -1,5 +1,11 @@
@import 'src/styles/variables';
@mixin mobile {
@media (max-width: $cell * 2) {
@content;
}
}
.cell {
&.horizontal,
&.quadro {

View file

@ -0,0 +1,33 @@
import { FC } from 'react';
import { NodeHorizontalCard } from '~/components/common/NodeHorizontalCard';
import { IFlowNode } from '~/types';
import styles from './styles.module.scss';
interface Props {
recent: IFlowNode[];
updated: IFlowNode[];
}
const FlowRecent: FC<Props> = ({ recent, updated }) => {
return (
<>
<div className={styles.updates}>
{updated &&
updated.map((node) => (
<NodeHorizontalCard node={node} key={node.id} hasNew />
))}
</div>
<div className={styles.recent}>
{recent &&
recent.map((node) => (
<NodeHorizontalCard node={node} key={node.id} />
))}
</div>
</>
);
};
export { FlowRecent };

View file

@ -0,0 +1,7 @@
@import 'src/styles/variables';
.recent {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}

View file

@ -2,15 +2,17 @@ import { FC, FormEvent, useCallback, useMemo } from 'react';
import classNames from 'classnames';
import { Card } from '~/components/common/Card';
import { Group } from '~/components/common/Group';
import { Icon } from '~/components/common/Icon';
import { NodeHorizontalCard } from '~/components/common/NodeHorizontalCard';
import { SubTitle } from '~/components/common/SubTitle';
import { Superpower } from '~/components/common/Superpower';
import { InputText } from '~/components/input/InputText';
import { Toggle } from '~/components/input/Toggle';
import { experimentalFeatures } from '~/constants/features';
import styles from '~/containers/flow/FlowStamp/styles.module.scss';
import { useFlowContext } from '~/utils/providers/FlowProvider';
import { useSearchContext } from '~/utils/providers/SearchProvider';
import { FlowRecent } from './components/FlowRecent';
import { FlowSearchResults } from './components/FlowSearchResults';
interface Props {
@ -62,55 +64,60 @@ const FlowStamp: FC<Props> = ({ isFluid, onToggleLayout }) => {
return (
<div className={styles.wrap}>
<Card className={styles.search}>
<form onSubmit={onSearchSubmit}>
<InputText
title="Поиск"
value={searchText}
handler={setSearchText}
suffix={after}
onKeyUp={onKeyUp}
/>
</form>
</Card>
<form className={styles.search} onSubmit={onSearchSubmit}>
<InputText
title="Поиск"
value={searchText}
handler={setSearchText}
suffix={after}
onKeyUp={onKeyUp}
/>
</form>
{searchText ? (
<Card className={styles.grid}>
<SubTitle>Результаты поиска</SubTitle>
<div className={styles.search_results}>
<div className={styles.grid}>
<div className={styles.label}>
<span className={styles.label_text}>Результаты поиска</span>
<span className="line" />
</div>
<div className={styles.items}>
<FlowSearchResults
hasMore={searchHasMore}
isLoading={searchIsLoading}
results={searchResults}
onLoadMore={onSearchLoadMore}
/>
</div>
</div>
</div>
) : (
<div className={styles.grid}>
<div className={classNames(styles.label, styles.whatsnew)}>
<span className={styles.label_text}>Что нового?</span>
<span className="line" />
</div>
<div className={styles.items}>
<FlowSearchResults
hasMore={searchHasMore}
isLoading={searchIsLoading}
results={searchResults}
onLoadMore={onSearchLoadMore}
/>
<FlowRecent updated={updates} recent={recent} />
</div>
</Card>
) : (
<Card
className={classNames(styles.grid, {
[styles.noUpdates]: !updates.length,
})}
>
<SubTitle>Что нового?</SubTitle>
</div>
)}
{updates.length > 0 && (
<div className={classNames(styles.items, styles.updates)}>
{updates.map((node) => (
<NodeHorizontalCard node={node} key={node.id} hasNew />
))}
</div>
)}
{recent.length > 0 && (
<div className={classNames(styles.items, styles.recent)}>
{recent.map((node) => (
<NodeHorizontalCard node={node} key={node.id} />
))}
</div>
)}
</Card>
{experimentalFeatures.liquidFlow && (
<Superpower>
<div className={styles.toggles}>
<Group
horizontal
onClick={onToggleLayout}
className={styles.fluid_toggle}
>
<Toggle value={isFluid} />
<div className={styles.toggles__label}>Жидкое течение</div>
</Group>
</div>
</Superpower>
)}
</div>
);

View file

@ -1,24 +1,22 @@
@import '~/styles/variables';
@import '../../../styles/variables';
.wrap {
display: flex;
flex-direction: column;
width: 100%;
gap: $gap;
}
.search {
background-color: var(--content_bg_lighter);
border-radius: $radius;
}
.grid {
@include outer_shadow();
display: flex;
justify-content: stretch;
flex-direction: column;
flex: 1;
border-radius: $radius;
position: relative;
background: $content_bg;
overflow: hidden;
gap: $gap;
padding: $gap;
&::after {
content: '';
@ -35,25 +33,49 @@
display: none;
}
}
&.noUpdates {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}
}
.items.recent {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
background-color: red;
}
}
.items {
padding: 0 $gap 0 $gap;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.label {
display: flex;
flex-direction: row;
min-width: 0;
padding: $gap;
border-radius: $radius;
@include title_with_line();
color: transparentize(white, $amount: 0.8);
&_search {
color: white;
padding-left: $gap * 1.2;
}
& > :global(.line) {
margin-right: $gap;
}
}
.label_text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search {
@include outer_shadow();
background: $content_bg_lighter;
padding: $gap;
border-radius: $radius;
}
.search_icon {
@ -67,3 +89,34 @@
stroke-width: 0.5;
transition: opacity 0.25s;
}
.toggles {
& > div {
padding: $gap;
font: $font_14_semibold;
}
&__label {
cursor: pointer;
}
}
.fluid_toggle {
@include desktop {
display: none;
}
}
.whatsnew {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}
.search_results {
overflow: auto;
@include tablet {
margin-top: $gap;
}
}

View file

@ -30,7 +30,7 @@ $cols: math.div($content_width, $cell);
min-height: 200px;
@include flow_grid() {
grid-template-rows: min(50vh, 33cqw);
grid-template-rows: 40vh;
}
@include flow_breakpoint(5);

View file

@ -1,3 +1,4 @@
$bold: 700;
$semibold: 600;
$regular: 400;
@ -5,20 +6,10 @@ $medium: 500;
$light: 300;
$extra_light: 200;
$font:
Montserrat,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
'Noto Sans',
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji';
$font: Montserrat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
$font_48_semibold: $semibold 48px $font;
$font_48_bold: $bold 48px $font;
@ -48,6 +39,5 @@ $font_8_regular: $regular 8px $font;
$font_8_semibold: $semibold 8px $font;
$font_cell_title: $font_24_semibold;
$font_cell_title_compact: $font_18_semibold;
$font_hero_title: $bold 40px $font;
$font_boris: $bold 72px $font;

View file

@ -63,8 +63,10 @@ $_brown: #23201f;
--content_bg_success: #{transparentize($_wisegreen, 0.7)};
--content_bg_info: #{transparentize($_blue, 0.5)};
--content_bg_danger: #{transparentize($_red, 0.5)};
--content_bg_backdrop: url('/images/noise.png')
#{transparentize($_brown, 0.3)};
--content_bg_backdrop: url('/images/noise.png') #{transparentize(
$_brown,
0.3
)};
--content_bg_hero: url('/images/noise.png') #{transparentize($_brown, 0.6)};
// white shades (move to --vars)

View file

@ -85,8 +85,10 @@ $_ocean: #25b0bc;
--gray_90: #{transparentize(white, 0.95)};
// page background
--page-background: 50% 50% / cover no-repeat url('/images/horizon_bg.svg')
#{darken($_cold, 4%)} fixed;
--page-background: 50% 50% / cover no-repeat url('/images/horizon_bg.svg') #{darken(
$_cold,
4%
)} fixed;
--page-background-top: linear-gradient(
#{$_accent} -150%,
#{transparentize($_ocean, 0.99)} 100px,

View file

@ -10,7 +10,6 @@ import {
COMMENT_BLOCK_TYPES,
ICommentBlock,
} from '~/constants/comment';
import { headerHeight } from '~/constants/dom';
import { IFile, ValueOf } from '~/types';
import { CONFIG } from '~/utils/config';
import {
@ -214,14 +213,3 @@ export const sizeOf = (bytes: number): string => {
(bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B'
);
};
/** Tells if element is in view */
export const isFullyVisible = (element?: HTMLElement): boolean => {
if (!element) {
return false;
}
const rect = element.getBoundingClientRect();
return rect?.top > headerHeight && rect?.bottom < window.innerHeight;
};