mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
added fluid flow toggle
This commit is contained in:
parent
337f8609c2
commit
8ef10d5273
5 changed files with 104 additions and 43 deletions
|
@ -1,23 +1,33 @@
|
||||||
import React, { FC, useCallback, FormEvent, useMemo, KeyboardEvent } from 'react';
|
import React, { FC, FormEvent, useCallback, useMemo } from 'react';
|
||||||
import { IFlowState } from '~/redux/flow/reducer';
|
import { IFlowState } from '~/redux/flow/reducer';
|
||||||
import { InputText } from '~/components/input/InputText';
|
import { InputText } from '~/components/input/InputText';
|
||||||
import { FlowRecent } from '../FlowRecent';
|
import { FlowRecent } from '../FlowRecent';
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import * as FLOW_ACTIONS from '~/redux/flow/actions';
|
|
||||||
import { FlowSearchResults } from '../FlowSearchResults';
|
import { FlowSearchResults } from '../FlowSearchResults';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
|
import { Group } from '~/components/containers/Group';
|
||||||
|
import { Toggle } from '~/components/input/Toggle';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
recent: IFlowState['recent'];
|
recent: IFlowState['recent'];
|
||||||
updated: IFlowState['updated'];
|
updated: IFlowState['updated'];
|
||||||
search: IFlowState['search'];
|
search: IFlowState['search'];
|
||||||
|
isFluid: boolean;
|
||||||
onSearchChange: (text: string) => void;
|
onSearchChange: (text: string) => void;
|
||||||
onLoadMore: () => void;
|
onLoadMore: () => void;
|
||||||
|
toggleLayout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FlowStamp: FC<IProps> = ({ recent, updated, search, onSearchChange, onLoadMore }) => {
|
const FlowStamp: FC<IProps> = ({
|
||||||
|
recent,
|
||||||
|
updated,
|
||||||
|
search,
|
||||||
|
onSearchChange,
|
||||||
|
onLoadMore,
|
||||||
|
isFluid,
|
||||||
|
toggleLayout,
|
||||||
|
}) => {
|
||||||
const onSearchSubmit = useCallback((event: FormEvent) => {
|
const onSearchSubmit = useCallback((event: FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -81,6 +91,13 @@ const FlowStamp: FC<IProps> = ({ recent, updated, search, onSearchChange, onLoad
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.toggles}>
|
||||||
|
<Group horizontal onClick={toggleLayout}>
|
||||||
|
<Toggle value={isFluid} />
|
||||||
|
<div className={styles.toggles__label}>Жидкое течение</div>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,3 +99,12 @@
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggles {
|
||||||
|
padding: $gap;
|
||||||
|
font: $font_14_semibold;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useCallback, useEffect, useMemo } from 'react';
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { FlowGrid } from '~/components/flow/FlowGrid';
|
import { FlowGrid } from '~/components/flow/FlowGrid';
|
||||||
import { selectFlow } from '~/redux/flow/selectors';
|
import { selectFlow } from '~/redux/flow/selectors';
|
||||||
|
@ -17,9 +17,17 @@ import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
|
import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
|
||||||
|
import { usePersistedState } from '~/utils/hooks/usePersistedState';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
enum Layout {
|
||||||
|
Fluid = 'fluid',
|
||||||
|
Default = 'default',
|
||||||
|
}
|
||||||
|
|
||||||
const FlowLayout: FC = () => {
|
const FlowLayout: FC = () => {
|
||||||
const { nodes, heroes, recent, updated, is_loading, search } = useShallowSelect(selectFlow);
|
const { nodes, heroes, recent, updated, is_loading, search } = useShallowSelect(selectFlow);
|
||||||
|
const [layout, setLayout] = usePersistedState('flow_layout', Layout.Default);
|
||||||
const labUpdates = useShallowSelect(selectLabUpdatesNodes);
|
const labUpdates = useShallowSelect(selectLabUpdatesNodes);
|
||||||
const user = useShallowSelect(selectUser);
|
const user = useShallowSelect(selectUser);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -58,6 +66,11 @@ const FlowLayout: FC = () => {
|
||||||
labUpdates,
|
labUpdates,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isFluid = layout === Layout.Fluid;
|
||||||
|
const toggleLayout = useCallback(() => {
|
||||||
|
setLayout(isFluid ? Layout.Default : Layout.Fluid);
|
||||||
|
}, [setLayout, isFluid]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('scroll', onLoadMore);
|
window.addEventListener('scroll', onLoadMore);
|
||||||
|
|
||||||
|
@ -69,7 +82,7 @@ const FlowLayout: FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<div className={classNames(styles.container, { [styles.fluid]: isFluid })}>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<div className={styles.hero}>
|
<div className={styles.hero}>
|
||||||
<FlowHero heroes={heroes} />
|
<FlowHero heroes={heroes} />
|
||||||
|
@ -80,8 +93,10 @@ const FlowLayout: FC = () => {
|
||||||
recent={recent}
|
recent={recent}
|
||||||
updated={cumulativeUpdates}
|
updated={cumulativeUpdates}
|
||||||
search={search}
|
search={search}
|
||||||
|
isFluid={isFluid}
|
||||||
onSearchChange={onChangeSearch}
|
onSearchChange={onChangeSearch}
|
||||||
onLoadMore={onLoadMoreSearch}
|
onLoadMore={onLoadMoreSearch}
|
||||||
|
toggleLayout={toggleLayout}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -89,7 +104,7 @@ const FlowLayout: FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SidebarRouter prefix="" />
|
<SidebarRouter prefix="" />
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,29 @@
|
||||||
|
|
||||||
$cols: $content_width / $cell;
|
$cols: $content_width / $cell;
|
||||||
|
|
||||||
|
@mixin fluid {
|
||||||
|
@media(min-width: 640px) {
|
||||||
|
.fluid & {
|
||||||
|
@content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: $content_width;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.fluid {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(auto-fit, minmax($cell - 5, 1fr));
|
||||||
grid-template-rows: 50vh $cell;
|
grid-template-rows: 50vh $cell;
|
||||||
grid-auto-rows: $cell;
|
grid-auto-rows: $cell;
|
||||||
|
|
||||||
|
@ -20,41 +39,14 @@ $cols: $content_width / $cell;
|
||||||
grid-column-gap: $gap;
|
grid-column-gap: $gap;
|
||||||
grid-row-gap: $gap;
|
grid-row-gap: $gap;
|
||||||
|
|
||||||
@include tablet {
|
@include fluid {
|
||||||
padding: 0 $gap;
|
grid-template-rows: $cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $cell * 6) {
|
@media (max-width: ($cell + 5) * 2) {
|
||||||
grid-template-columns: repeat(5, 1fr);
|
// rework stamp, so it will be shown as smaller one on mobiles
|
||||||
grid-template-rows: 50vh 20vw;
|
grid-template-columns: repeat(auto-fit, minmax($cell / 1.5 - 5, 1fr));
|
||||||
grid-auto-rows: 20vw;
|
grid-template-rows: 50vh $cell;
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell * 5) {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
grid-template-rows: 40vh 25vw;
|
|
||||||
grid-auto-rows: 25vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell * 4) {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
grid-template-rows: 40vh 33vw;
|
|
||||||
grid-auto-rows: 33vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell * 3) {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
grid-template-rows: 40vh 50vw;
|
|
||||||
grid-auto-rows: 50vw;
|
|
||||||
grid-column-gap: $gap / 2;
|
|
||||||
grid-row-gap: $gap / 2;
|
|
||||||
padding: 0 $gap / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell * 2) {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
grid-template-rows: 40vh 50vw;
|
|
||||||
grid-auto-rows: 50vw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +56,7 @@ $cols: $content_width / $cell;
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
grid-row-end: span 1;
|
grid-row-end: 2;
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
grid-column-end: -1;
|
grid-column-end: -1;
|
||||||
background: darken($content_bg, 2%);
|
background: darken($content_bg, 2%);
|
||||||
|
@ -73,10 +65,19 @@ $cols: $content_width / $cell;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font: $font_24_semibold;
|
font: $font_24_semibold;
|
||||||
|
|
||||||
|
@include fluid {
|
||||||
|
grid-row-end: span 2;
|
||||||
|
grid-column-end: span 4;
|
||||||
|
|
||||||
|
@media(max-width: ($cell + 5) * 3) {
|
||||||
|
grid-column-end: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stamp {
|
.stamp {
|
||||||
grid-row-end: span 2;
|
grid-row-end: span 3;
|
||||||
grid-column: -2 / -1;
|
grid-column: -2 / -1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
19
src/utils/hooks/usePersistedState.ts
Normal file
19
src/utils/hooks/usePersistedState.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export const usePersistedState = (key: string, initial: string): [string, (val: string) => any] => {
|
||||||
|
const stored = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(`vault_${key}`) || initial;
|
||||||
|
} catch (e) {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
}, [key]);
|
||||||
|
|
||||||
|
const [val, setVal] = useState<string>(stored);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(`vault_${key}`, val);
|
||||||
|
}, [val]);
|
||||||
|
|
||||||
|
return [val, setVal];
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue