1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 12:56:41 +07:00

added working profile sidebar

This commit is contained in:
Fedor Katurov 2022-02-15 16:11:29 +07:00
parent c6c7dbe75d
commit 07b4874f69
16 changed files with 202 additions and 84 deletions

View file

@ -13,7 +13,7 @@ const TabContext = createContext({
setActiveTab: (activeTab: number) => {}, setActiveTab: (activeTab: number) => {},
}); });
const List: VFC<TabProps> = ({ items }) => { const HorizontalList: VFC<TabProps> = ({ items }) => {
const { activeTab, setActiveTab } = useContext(TabContext); const { activeTab, setActiveTab } = useContext(TabContext);
return ( return (
@ -54,7 +54,7 @@ const Tabs = function({ children }) {
return <TabContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabContext.Provider>; return <TabContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabContext.Provider>;
}; };
Tabs.List = List; Tabs.Horizontal = HorizontalList;
Tabs.Content = Content; Tabs.Content = Content;
export { Tabs }; export { Tabs };

View file

@ -3,23 +3,34 @@ import React, { FC } from 'react';
import { Filler } from '~/components/containers/Filler'; import { Filler } from '~/components/containers/Filler';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { ProfileSettings } from '~/components/profile/ProfileSettings'; import { ProfileSettings } from '~/components/profile/ProfileSettings';
import { useStackContext } from '~/components/sidebar/SidebarStack';
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
interface IProps {} interface IProps {}
const ProfileSidebarSettings: FC<IProps> = () => ( const ProfileSidebarSettings: FC<IProps> = () => {
<div className={styles.wrap}> const { closeAllTabs } = useStackContext();
<div className={styles.scroller}>
<ProfileSettings />
</div>
<div className={styles.buttons}> return (
<Filler /> <SidebarStackCard width={600} headerFeature="back" title="Настройки" onBackPress={closeAllTabs}>
<Button color="outline">Отмена</Button> <div className={styles.wrap}>
<Button>Сохранить</Button> <div className={styles.scroller}>
</div> <ProfileSettings />
</div> </div>
);
<div className={styles.buttons}>
<Filler />
<Button color="outline" onClick={closeAllTabs}>
Отмена
</Button>
<Button color="secondary">Сохранить</Button>
</div>
</div>
</SidebarStackCard>
);
};
export { ProfileSidebarSettings }; export { ProfileSidebarSettings };

View file

@ -6,6 +6,7 @@
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
z-index: 4; z-index: 4;
height: 100%;
} }
.scroller { .scroller {
@ -15,6 +16,8 @@
} }
.buttons { .buttons {
@include outer_shadow;
width: 100%; width: 100%;
padding: $gap; padding: $gap;
box-sizing: border-box; box-sizing: border-box;

View file

@ -1,10 +1,36 @@
import React, { FC, useMemo } from 'react'; import React, {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import { isNil } from '~/utils/ramda';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
interface SidebarStackProps {} interface SidebarStackProps extends PropsWithChildren<{}> {
initialTab?: number;
}
interface SidebarStackContextValue {
activeTab?: number;
setActiveTab: (val: number) => void;
closeAllTabs: () => void;
}
const SidebarStackContext = createContext<SidebarStackContextValue>({
activeTab: undefined,
setActiveTab: () => {},
closeAllTabs: () => {},
});
const SidebarCards: FC = ({ children }) => {
const { activeTab } = useStackContext();
const SidebarStack: FC<SidebarStackProps> = ({ children }) => {
const nonEmptyChildren = useMemo(() => { const nonEmptyChildren = useMemo(() => {
if (!children) { if (!children) {
return []; return [];
@ -13,15 +39,26 @@ const SidebarStack: FC<SidebarStackProps> = ({ children }) => {
return Array.isArray(children) ? children.filter(it => it) : [children]; return Array.isArray(children) ? children.filter(it => it) : [children];
}, [children]); }, [children]);
if (isNil(activeTab) || !nonEmptyChildren[activeTab]) {
return null;
}
return <div className={styles.card}>{nonEmptyChildren[activeTab]}</div>;
};
const SidebarStack = function({ children, initialTab }: SidebarStackProps) {
const [activeTab, setActiveTab] = useState<number | undefined>(initialTab);
const closeAllTabs = useCallback(() => setActiveTab(undefined), []);
return ( return (
<div className={styles.stack}> <SidebarStackContext.Provider value={{ activeTab, setActiveTab, closeAllTabs }}>
{nonEmptyChildren.map((child, i) => ( <div className={styles.stack}>{children}</div>
<div className={styles.card} key={i}> </SidebarStackContext.Provider>
{child}
</div>
))}
</div>
); );
}; };
export { SidebarStack }; SidebarStack.Cards = SidebarCards;
const useStackContext = () => useContext(SidebarStackContext);
export { SidebarStack, useStackContext };

View file

@ -6,6 +6,12 @@
flex-direction: row-reverse; flex-direction: row-reverse;
width: auto; width: auto;
border-radius: $radius 0 0 $radius; border-radius: $radius 0 0 $radius;
@include sidebar_stack_limit {
& > *:not(:last-child) {
display: none;
}
}
} }
.card { .card {

View file

@ -9,19 +9,28 @@ interface SidebarStackCardProps {
width?: number; width?: number;
headerFeature?: 'back' | 'close'; headerFeature?: 'back' | 'close';
title?: string; title?: string;
onBackPress?: () => void;
} }
const SidebarStackCard: FC<SidebarStackCardProps> = ({ children, title, width, headerFeature }) => { const SidebarStackCard: FC<SidebarStackCardProps> = ({
children,
title,
width,
headerFeature,
onBackPress,
}) => {
const style = useMemo(() => ({ maxWidth: width, flexBasis: width }), [width]); const style = useMemo(() => ({ maxWidth: width, flexBasis: width }), [width]);
const backIcon = headerFeature === 'close' ? 'close' : 'right';
return ( return (
<div style={style} className={styles.card}> <div style={style} className={styles.card}>
{(headerFeature || title) && ( {!!(headerFeature || title) && (
<div className={styles.head}> <div className={styles.head}>
<Filler>{!!title && <h5>{title}</h5>}</Filler> <Filler>{!!title && <h6>{title}</h6>}</Filler>
{headerFeature === 'back' && <Button color="link" iconRight="right" />} {!!(headerFeature && onBackPress) && (
{headerFeature === 'close' && <Button color="link" iconRight="close" />} <Button color="link" iconRight={backIcon} onClick={onBackPress} />
)}
</div> </div>
)} )}

View file

@ -17,10 +17,12 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding: $gap $gap $gap $gap * 2; padding: $gap $gap $gap $gap * 2;
height: 48px;
} }
.content { .content {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
} }

View file

@ -8,6 +8,7 @@ import { Filler } from '~/components/containers/Filler';
import { Grid } from '~/components/containers/Grid'; import { Grid } from '~/components/containers/Grid';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { useStackContext } from '~/components/sidebar/SidebarStack';
import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead'; import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead';
import markdown from '~/styles/common/markdown.module.scss'; import markdown from '~/styles/common/markdown.module.scss';
@ -17,54 +18,58 @@ interface ProfileSidebarMenuProps {
onClose: () => void; onClose: () => void;
} }
const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => ( const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => {
<div className={styles.wrap}> const { setActiveTab } = useStackContext();
<div>
<ProfileSidebarHead /> return (
<div className={styles.wrap}>
<div>
<ProfileSidebarHead />
</div>
<Filler className={classNames(markdown.wrapper, styles.text)}>
<Group>
<ul className={styles.menu}>
<li onClick={() => setActiveTab(0)}>Настройки</li>
<li onClick={() => setActiveTab(1)}>Заметки</li>
<li onClick={() => setActiveTab(2)}>Удалённые посты</li>
</ul>
<Grid columns="2fr 1fr">
<Card>
<h4>1 год 2 месяца</h4>
<small>в убежище</small>
</Card>
<Card>
<Square>
<h4>24 поста</h4>
<small>Создано</small>
</Square>
</Card>
</Grid>
<Grid columns="1fr 2fr">
<Card>
<Square>
<h4>16545 лайка</h4>
<small>получено</small>
</Square>
</Card>
<Card>
<h4>123123 комментария</h4>
<small>под постами</small>
</Card>
</Grid>
</Group>
</Filler>
<Button round onClick={onClose} color="secondary">
Закрыть
</Button>
</div> </div>
);
<Filler className={classNames(markdown.wrapper, styles.text)}> };
<Group>
<ul className={styles.menu}>
<li>Настройки</li>
<li>Заметки</li>
<li>Удалённые посты</li>
</ul>
<Grid columns="2fr 1fr">
<Card>
<h4>1 год 2 месяца</h4>
<small>в убежище</small>
</Card>
<Card>
<Square>
<h4>24 поста</h4>
<small>Создано</small>
</Square>
</Card>
</Grid>
<Grid columns="1fr 2fr">
<Card>
<Square>
<h4>16545 лайка</h4>
<small>получено</small>
</Square>
</Card>
<Card>
<h4>123123 комментария</h4>
<small>под постами</small>
</Card>
</Grid>
</Group>
</Filler>
<Button round onClick={onClose} color="secondary">
Закрыть
</Button>
</div>
);
export { ProfileSidebarMenu }; export { ProfileSidebarMenu };

View file

@ -3,6 +3,9 @@
.wrap { .wrap {
padding: $gap; padding: $gap;
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
} }
.text { .text {

View file

@ -13,7 +13,7 @@ const ProfileTabs: FC<IProps> = ({ is_own }) => {
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
<Tabs.List items={items} /> <Tabs.Horizontal items={items} />
</div> </div>
); );
}; };

View file

@ -1,5 +1,6 @@
import React, { VFC } from 'react'; import React, { VFC } from 'react';
import { Tabs } from '~/components/dialogs/Tabs';
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings'; import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
import { SidebarStack } from '~/components/sidebar/SidebarStack'; import { SidebarStack } from '~/components/sidebar/SidebarStack';
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard'; import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
@ -13,13 +14,13 @@ const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => {
return ( return (
<SidebarWrapper onClose={onRequestClose}> <SidebarWrapper onClose={onRequestClose}>
<SidebarStack> <SidebarStack>
<SidebarStackCard headerFeature="close" title="Профиль"> <SidebarStackCard headerFeature="close" title="Профиль" onBackPress={onRequestClose}>
<ProfileSidebarMenu onClose={onRequestClose} /> <ProfileSidebarMenu onClose={onRequestClose} />
</SidebarStackCard> </SidebarStackCard>
<SidebarStackCard width={600} headerFeature="back" title="Настройки"> <SidebarStack.Cards>
<ProfileSidebarSettings /> <ProfileSidebarSettings />
</SidebarStackCard> </SidebarStack.Cards>
</SidebarStack> </SidebarStack>
</SidebarWrapper> </SidebarWrapper>
); );

View file

@ -8,9 +8,10 @@ import styles from './styles.module.scss';
interface IProps { interface IProps {
onClose?: () => void; onClose?: () => void;
closeOnBackdropClick?: boolean;
} }
const SidebarWrapper: FC<IProps> = ({ children, onClose }) => { const SidebarWrapper: FC<IProps> = ({ children, onClose, closeOnBackdropClick = true }) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
useCloseOnEscape(onClose); useCloseOnEscape(onClose);
@ -24,6 +25,7 @@ const SidebarWrapper: FC<IProps> = ({ children, onClose }) => {
return ( return (
<div className={styles.wrapper} ref={ref}> <div className={styles.wrapper} ref={ref}>
{closeOnBackdropClick && <div className={styles.backdrop} onClick={onClose} />}
{children} {children}
</div> </div>
); );

View file

@ -23,3 +23,13 @@
z-index: 4; z-index: 4;
} }
} }
.backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
cursor: pointer;
}

View file

@ -1,12 +1,17 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { BorisSidebar } from '~/components/boris/BorisSidebar'; import { BorisSidebar } from '~/components/boris/BorisSidebar';
import { Superpower } from '~/components/boris/Superpower';
import { Card } from '~/components/containers/Card'; import { Card } from '~/components/containers/Card';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder';
import { Sticky } from '~/components/containers/Sticky'; import { Sticky } from '~/components/containers/Sticky';
import { Button } from '~/components/input/Button';
import { Dialog } from '~/constants/modal';
import { BorisComments } from '~/containers/boris/BorisComments'; import { BorisComments } from '~/containers/boris/BorisComments';
import { Container } from '~/containers/main/Container'; import { Container } from '~/containers/main/Container';
import { SidebarRouter } from '~/containers/main/SidebarRouter'; import { SidebarRouter } from '~/containers/main/SidebarRouter';
import { useShowModal } from '~/hooks/modal/useShowModal';
import { BorisUsageStats } from '~/types/boris'; import { BorisUsageStats } from '~/types/boris';
import { useAuthProvider } from '~/utils/providers/AuthProvider'; import { useAuthProvider } from '~/utils/providers/AuthProvider';
@ -22,6 +27,7 @@ type IProps = {
const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLoadingStats }) => { const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLoadingStats }) => {
const { isUser } = useAuthProvider(); const { isUser } = useAuthProvider();
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar);
return ( return (
<Container> <Container>
@ -38,6 +44,14 @@ const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLo
<div className={styles.container}> <div className={styles.container}>
<Card className={styles.content}> <Card className={styles.content}>
<Superpower>
<Padder>
<h2>Тестовые фичи</h2>
<Button onClick={() => openProfileSidebar({})}>Профиль в сайдбаре</Button>
</Padder>
<Padder />
</Superpower>
<BorisComments /> <BorisComments />
</Card> </Card>

View file

@ -129,11 +129,16 @@ button {
outline: none; outline: none;
} }
h5, h4, h3, h2, h1 { h3, h2, h1 {
color: white; color: white;
font-weight: 800; font-weight: 800;
} }
h6, h5, h4 {
color: white;
font-weight: 600;
}
h1 { h1 {
font-size: 2em; font-size: 2em;
} }
@ -154,6 +159,10 @@ h5 {
font-size: 1.2em; font-size: 1.2em;
} }
h6 {
font-size: 1em;
}
p { p {
margin-bottom: $gap; margin-bottom: $gap;

View file

@ -85,6 +85,12 @@
} }
} }
@mixin sidebar_stack_limit {
@media (max-width: 1000px) {
@content;
}
}
@mixin desktop { @mixin desktop {
@media (max-width: $content_width) { @media (max-width: $content_width) {
@content; @content;