mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
made better tabs (with context)
This commit is contained in:
parent
f63d2d3228
commit
da510e346a
12 changed files with 103 additions and 194 deletions
|
@ -1,16 +0,0 @@
|
||||||
import React, { FC, MouseEventHandler } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import styles from './styles.module.scss';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
active?: boolean;
|
|
||||||
onClick?: MouseEventHandler<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tab: FC<IProps> = ({ active, onClick, children }) => (
|
|
||||||
<div className={classNames(styles.tab, { [styles.active]: active })} onClick={onClick}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export { Tab };
|
|
|
@ -1,20 +0,0 @@
|
||||||
@import "src/styles/variables";
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
@include outer_shadow();
|
|
||||||
|
|
||||||
padding: $gap;
|
|
||||||
margin-right: $gap;
|
|
||||||
border-radius: $radius $radius 0 0;
|
|
||||||
font: $font_14_semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: $content_bg;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: lighten($content_bg, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,51 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { createContext, FC, VFC, useContext, useState } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { IAuthState } from '~/redux/auth/types';
|
|
||||||
|
|
||||||
interface IProps {}
|
interface TabProps {
|
||||||
|
items: string[];
|
||||||
|
}
|
||||||
|
|
||||||
const Tabs: FC<IProps> = ({ children }) => {
|
const TabContext = createContext({
|
||||||
return <div className={styles.wrap}>{children}</div>;
|
activeTab: 0,
|
||||||
|
setActiveTab: (activeTab: number) => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const List: VFC<TabProps> = ({ items }) => {
|
||||||
|
const { activeTab, setActiveTab } = useContext(TabContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.tabs}>
|
||||||
|
{items.map((it, index) => (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.tab, { [styles.active]: activeTab === index })}
|
||||||
|
onClick={() => setActiveTab(index)}
|
||||||
|
key={it}
|
||||||
|
>
|
||||||
|
{it}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Content: FC<any> = ({ children }) => {
|
||||||
|
const { activeTab } = useContext(TabContext);
|
||||||
|
|
||||||
|
if (!Array.isArray(children) && activeTab > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(children) ? children[activeTab] : children;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tabs = function({ children }) {
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
|
return <TabContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
Tabs.List = List;
|
||||||
|
Tabs.Content = Content;
|
||||||
|
|
||||||
export { Tabs };
|
export { Tabs };
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import "src/styles/variables";
|
@import "~/styles/variables";
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -6,3 +6,27 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0 $gap / 2;
|
padding: 0 $gap / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
@include outer_shadow();
|
||||||
|
|
||||||
|
padding: $gap;
|
||||||
|
margin-right: $gap;
|
||||||
|
border-radius: $radius $radius 0 0;
|
||||||
|
font: $font_14_semibold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $content_bg;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: lighten($content_bg, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
|
@ -76,10 +76,6 @@
|
||||||
background: lighten($content_bg, 3%);
|
background: lighten($content_bg, 3%);
|
||||||
padding: $gap;
|
padding: $gap;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
|
|
||||||
:global(.input_title) {
|
|
||||||
color: lighten($content_bg, 10%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_icon {
|
.search_icon {
|
||||||
|
|
|
@ -84,7 +84,7 @@ const InputText: FC<IInputTextProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{title && (
|
{title && (
|
||||||
<div className={classNames(styles.title, 'input_title')}>
|
<div className={classNames(styles.title)}>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import styles from './styles.module.scss';
|
|
||||||
|
|
||||||
interface ITextInputProps {
|
|
||||||
type?: 'text' | 'password';
|
|
||||||
placeholder?: string;
|
|
||||||
label?: string;
|
|
||||||
value?: string;
|
|
||||||
|
|
||||||
onChange: React.ChangeEventHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextInput: React.FunctionComponent<ITextInputProps> = ({
|
|
||||||
type = 'text',
|
|
||||||
placeholder = '',
|
|
||||||
label,
|
|
||||||
onChange = () => {},
|
|
||||||
value = '',
|
|
||||||
}) => (
|
|
||||||
<div
|
|
||||||
className={styles.wrapper}
|
|
||||||
>
|
|
||||||
<div className={styles.container}>
|
|
||||||
{
|
|
||||||
label
|
|
||||||
&& <div className={styles.label}>{label}</div>
|
|
||||||
}
|
|
||||||
<input
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={styles.input}
|
|
||||||
type={type}
|
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
|
@ -1,58 +0,0 @@
|
||||||
@import "src/styles/variables";
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
height: $input_height;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
position: relative;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
// background: transparentize(black, 0.8);
|
|
||||||
font: $font_14_medium;
|
|
||||||
// color: transparentize(white, 0.5);
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 2px $gap;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
border-radius: $radius 0 0 $radius;
|
|
||||||
//@include input_shadow();
|
|
||||||
|
|
||||||
padding: 0 5px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: $input_height - 3px;
|
|
||||||
font: $font_10_semibold;
|
|
||||||
background-color: white;
|
|
||||||
color: transparentize(white, 0.5);
|
|
||||||
border-radius: $radius;
|
|
||||||
left: 6px;
|
|
||||||
background: $content_bg;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: $input_height;
|
|
||||||
background: $input_bg_color;
|
|
||||||
border-radius: $input_radius;
|
|
||||||
flex-direction: row;
|
|
||||||
flex: 1 0;
|
|
||||||
display: flex;
|
|
||||||
align-self: stretch;
|
|
||||||
align-items: stretch;
|
|
||||||
justify-content: center;
|
|
||||||
@include input_shadow();
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
font-size: inherit;
|
|
||||||
color: white;
|
|
||||||
padding: 0 $gap;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
|
@ -4,22 +4,16 @@ import { ProfileInfo } from '~/containers/profile/ProfileInfo';
|
||||||
import { IDialogProps } from '~/redux/types';
|
import { IDialogProps } from '~/redux/types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
|
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
|
||||||
import { ProfileMessages } from '~/containers/profile/ProfileMessages';
|
|
||||||
import { ProfileDescription } from '~/components/profile/ProfileDescription';
|
|
||||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||||
import { IAuthState } from '~/redux/auth/types';
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
import { pick } from 'ramda';
|
import { pick } from 'ramda';
|
||||||
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||||
|
import { MessageForm } from '~/components/profile/MessageForm';
|
||||||
|
import { Tabs } from '~/components/dialogs/Tabs';
|
||||||
|
import { ProfileDescription } from '~/components/profile/ProfileDescription';
|
||||||
|
import { ProfileMessages } from '~/containers/profile/ProfileMessages';
|
||||||
import { ProfileSettings } from '~/components/profile/ProfileSettings';
|
import { ProfileSettings } from '~/components/profile/ProfileSettings';
|
||||||
import { ProfileAccounts } from '~/components/profile/ProfileAccounts';
|
import { ProfileAccounts } from '~/components/profile/ProfileAccounts';
|
||||||
import { MessageForm } from '~/components/profile/MessageForm';
|
|
||||||
|
|
||||||
const TAB_CONTENT = {
|
|
||||||
profile: <ProfileDescription />,
|
|
||||||
messages: <ProfileMessages />,
|
|
||||||
settings: <ProfileSettings />,
|
|
||||||
accounts: <ProfileAccounts />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
profile: selectAuthProfile(state),
|
profile: selectAuthProfile(state),
|
||||||
|
@ -52,23 +46,30 @@ const ProfileDialogUnconnected: FC<IProps> = ({
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BetterScrollDialog
|
<Tabs>
|
||||||
header={
|
<BetterScrollDialog
|
||||||
<ProfileInfo
|
header={
|
||||||
is_own={user && user.id === id}
|
<ProfileInfo
|
||||||
is_loading={is_loading}
|
is_own={user && user.id === id}
|
||||||
user={user}
|
is_loading={is_loading}
|
||||||
tab={tab}
|
user={user}
|
||||||
setTab={setTab}
|
tab={tab}
|
||||||
content={PROFILE_HEADERS[tab]}
|
setTab={setTab}
|
||||||
/>
|
content={PROFILE_HEADERS[tab]}
|
||||||
}
|
/>
|
||||||
footer={PROFILE_FOOTERS[tab]}
|
}
|
||||||
backdrop={<CoverBackdrop cover={user && user.cover} />}
|
footer={PROFILE_FOOTERS[tab]}
|
||||||
onClose={onRequestClose}
|
backdrop={<CoverBackdrop cover={user && user.cover} />}
|
||||||
>
|
onClose={onRequestClose}
|
||||||
{TAB_CONTENT[tab] || null}
|
>
|
||||||
</BetterScrollDialog>
|
<Tabs.Content>
|
||||||
|
<ProfileDescription />
|
||||||
|
<ProfileMessages />
|
||||||
|
<ProfileSettings />
|
||||||
|
<ProfileAccounts />
|
||||||
|
</Tabs.Content>
|
||||||
|
</BetterScrollDialog>
|
||||||
|
</Tabs>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { IAuthState } from '~/redux/auth/types';
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
import { Tabs } from '~/components/dialogs/Tabs';
|
import { Tabs } from '~/components/dialogs/Tabs';
|
||||||
import { Tab } from '~/components/dialogs/Tab';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tab: string;
|
tab: string;
|
||||||
|
@ -12,30 +10,11 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => {
|
const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => {
|
||||||
const changeTab = useCallback(
|
const items = ['Профиль', 'Сообщения', ...(is_own ? ['Настройки'] : [])];
|
||||||
(tab: IAuthState['profile']['tab']) => () => {
|
|
||||||
if (!setTab) return;
|
|
||||||
setTab(tab);
|
|
||||||
},
|
|
||||||
[setTab]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Tabs>
|
<Tabs.List items={items} />
|
||||||
<Tab active={tab === 'profile'} onClick={changeTab('profile')}>
|
|
||||||
Профиль
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab active={tab === 'messages'} onClick={changeTab('messages')}>
|
|
||||||
Сообщения
|
|
||||||
</Tab>
|
|
||||||
{is_own && (
|
|
||||||
<Tab active={tab === 'settings'} onClick={changeTab('settings')}>
|
|
||||||
Настройки
|
|
||||||
</Tab>
|
|
||||||
)}
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||||
import { BorisSidebar } from '~/components/boris/BorisSidebar';
|
import { BorisSidebar } from '~/components/boris/BorisSidebar';
|
||||||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
import { useUserContext } from '~/utils/context/UserContextProvider';
|
||||||
import { BorisUsageStats } from '~/redux/boris/reducer';
|
import { BorisUsageStats } from '~/redux/boris/reducer';
|
||||||
|
import { Tabs } from '~/components/dialogs/Tabs';
|
||||||
|
import { Superpower } from '~/components/boris/Superpower';
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
border-radius: $input_radius;
|
border-radius: $input_radius;
|
||||||
//box-shadow: $input_shadow;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -274,7 +273,7 @@
|
||||||
transition: top 0.25s, bottom 0.25s, font 0.25s, color 0.25s;
|
transition: top 0.25s, bottom 0.25s, font 0.25s, color 0.25s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
color: transparentize(white, 0.5);
|
color: lighten($content_bg, 10%);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
background: $input_bg_color;
|
background: $input_bg_color;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue