1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

added sidebar router

This commit is contained in:
Fedor Katurov 2022-07-27 16:29:22 +07:00
parent 8a4709103b
commit 8a71d3d462
18 changed files with 166 additions and 108 deletions

View file

@ -1,6 +1,6 @@
#NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/ NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/ NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
#NEXT_PUBLIC_API_HOST=http://localhost:8888/ #NEXT_PUBLIC_API_HOST=http://localhost:8888/
#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/ #NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
NEXT_PUBLIC_API_HOST=https://pig.vault48.org/ #NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/ #NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/

1
.gitignore vendored
View file

@ -5,3 +5,4 @@
/build /build
/.next /.next
/.vscode /.vscode
/.history

View file

@ -1,11 +1,9 @@
import React, { FC, useState } from 'react'; import { FC, useState } from 'react';
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 { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { InputText } from '~/components/input/InputText'; import { InputText } from '~/components/input/InputText';
import { Dialog } from '~/constants/modal';
import { useShowModal } from '~/hooks/modal/useShowModal';
import markdown from '~/styles/common/markdown.module.scss'; import markdown from '~/styles/common/markdown.module.scss';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -14,7 +12,6 @@ interface IProps {}
const BorisUIDemo: FC<IProps> = () => { const BorisUIDemo: FC<IProps> = () => {
const [text, setText] = useState(''); const [text, setText] = useState('');
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar);
return ( return (
<Card className={styles.card}> <Card className={styles.card}>
@ -25,9 +22,6 @@ const BorisUIDemo: FC<IProps> = () => {
разработке разработке
</p> </p>
<h2>Тестовые фичи</h2>
<Button onClick={() => openProfileSidebar({})}>Профиль в сайдбаре</Button>
<h2>Инпуты</h2> <h2>Инпуты</h2>
<form autoComplete="off"> <form autoComplete="off">

View file

@ -17,11 +17,11 @@ const NodeTags: FC<IProps> = memo(
return ( return (
<Tags <Tags
tags={tags} tags={tags}
is_editable={is_editable} editable={is_editable}
onTagsChange={onChange} onTagsChange={onChange}
onTagClick={onTagClick} onTagClick={onTagClick}
onTagDelete={onTagDelete} onTagDelete={onTagDelete}
is_deletable={is_deletable} deletable={is_deletable}
/> />
); );
} }

View file

@ -10,7 +10,7 @@ interface IProps {
} }
const NodeTagsPlaceholder: FC<IProps> = memo(({ is_editable, tags, onChange }) => ( const NodeTagsPlaceholder: FC<IProps> = memo(({ is_editable, tags, onChange }) => (
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} /> <Tags tags={tags} editable={is_editable} onTagsChange={onChange} />
)); ));
export { NodeTagsPlaceholder }; export { NodeTagsPlaceholder };

View file

@ -1,4 +1,4 @@
import React, { VFC } from 'react'; import { VFC } from 'react';
import Link from 'next/link'; import Link from 'next/link';
@ -8,7 +8,6 @@ import { Button } from '~/components/input/Button';
import { VerticalMenu } from '~/components/menu/VerticalMenu'; import { VerticalMenu } from '~/components/menu/VerticalMenu';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead'; import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead';
import { ProfileStats } from '~/containers/profile/ProfileStats';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -18,8 +17,6 @@ const SettingsMenu: VFC<SettingsMenuProps> = () => (
<Group> <Group>
<ProfileSidebarHead /> <ProfileSidebarHead />
<br />
<Group> <Group>
<VerticalMenu className={styles.menu}> <VerticalMenu className={styles.menu}>
<Link href={URLS.SETTINGS.BASE} passHref> <Link href={URLS.SETTINGS.BASE} passHref>
@ -35,10 +32,6 @@ const SettingsMenu: VFC<SettingsMenuProps> = () => (
</Link> </Link>
</VerticalMenu> </VerticalMenu>
<div className={styles.stats}>
<ProfileStats />
</div>
<Group horizontal> <Group horizontal>
<Filler /> <Filler />

View file

@ -13,9 +13,9 @@ interface IProps {
tag: Partial<ITag>; tag: Partial<ITag>;
size?: 'normal' | 'big'; size?: 'normal' | 'big';
is_deletable?: boolean; deletable?: boolean;
is_hoverable?: boolean; hoverable?: boolean;
is_editing?: boolean; editing?: boolean;
onBlur?: FocusEventHandler<HTMLInputElement>; onBlur?: FocusEventHandler<HTMLInputElement>;
onClick?: (tag: Partial<ITag>) => void; onClick?: (tag: Partial<ITag>) => void;
@ -24,9 +24,9 @@ interface IProps {
const Tag: FC<IProps> = ({ const Tag: FC<IProps> = ({
tag, tag,
is_deletable, deletable: deletable,
is_hoverable, hoverable: hoverable,
is_editing, editing: editing,
size = 'normal', size = 'normal',
onClick, onClick,
onDelete, onDelete,
@ -48,9 +48,9 @@ const Tag: FC<IProps> = ({
<TagWrapper <TagWrapper
feature={getTagFeature(tag)} feature={getTagFeature(tag)}
size={size} size={size}
is_deletable={is_deletable} deletable={deletable}
is_hoverable={is_hoverable} hoverable={hoverable}
is_editing={is_editing} editing={editing}
onClick={onClick && onClickHandler} onClick={onClick && onClickHandler}
onDelete={onDeleteHandler} onDelete={onDeleteHandler}
title={tag.title} title={tag.title}

View file

@ -9,10 +9,10 @@ import styles from './styles.module.scss';
interface IProps { interface IProps {
feature?: string; feature?: string;
size?: string; size?: string;
is_deletable?: boolean; deletable?: boolean;
is_hoverable?: boolean; hoverable?: boolean;
is_editing?: boolean; editing?: boolean;
has_input?: boolean; hasInput?: boolean;
onClick?: () => void; onClick?: () => void;
onDelete?: () => void; onDelete?: () => void;
title?: string; title?: string;
@ -22,15 +22,15 @@ const TagWrapper: FC<IProps> = ({
children, children,
feature, feature,
size, size,
is_deletable, deletable,
is_hoverable, hoverable,
is_editing, editing,
has_input, hasInput,
onClick, onClick,
onDelete, onDelete,
title = '', title = '',
}) => { }) => {
const deletable = is_deletable && !is_editing && !has_input; const canBeDeleted = deletable && !editing && !hasInput;
const onDeletePress = useCallback( const onDeletePress = useCallback(
event => { event => {
if (!onDelete) { if (!onDelete) {
@ -46,10 +46,10 @@ const TagWrapper: FC<IProps> = ({
return ( return (
<div <div
className={classNames(styles.tag, feature, size, { className={classNames(styles.tag, feature, size, {
is_hoverable, is_hoverable: hoverable,
is_editing, is_editing: editing,
deletable, deletable: canBeDeleted,
input: has_input, input: hasInput,
clickable: onClick, clickable: onClick,
})} })}
onClick={onClick} onClick={onClick}

View file

@ -0,0 +1,11 @@
import { FC, ReactNode } from "react";
import { ProfileSidebar } from "~/containers/sidebars/ProfileSidebar";
export enum SidebarName {
Settings = 'settings'
}
export const sidebarComponents = {
[SidebarName.Settings]: ProfileSidebar
}

View file

@ -1,19 +1,22 @@
import { FC } from 'react'; import { FC, useCallback } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
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 { Dialog } from '~/constants/modal'; import { SidebarName } from '~/constants/sidebar';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { useShowModal } from '~/hooks/modal/useShowModal'; import { useSidebar } from '~/utils/providers/SidebarProvider';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
export interface BorisSuperpowersProps {} export interface BorisSuperpowersProps {}
const BorisSuperpowers: FC<BorisSuperpowersProps> = () => { const BorisSuperpowers: FC<BorisSuperpowersProps> = () => {
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar); const { open } = useSidebar();
const openProfileSidebar = useCallback(() => {
open(SidebarName.Settings);
}, [open]);
const { push } = useRouter(); const { push } = useRouter();
return ( return (
@ -21,7 +24,7 @@ const BorisSuperpowers: FC<BorisSuperpowersProps> = () => {
<h2>Штучи, находящиеся в разработке</h2> <h2>Штучи, находящиеся в разработке</h2>
<div className={styles.grid}> <div className={styles.grid}>
<Button size="mini" onClick={() => openProfileSidebar({})}> <Button size="mini" onClick={() => openProfileSidebar()}>
Открыть Открыть
</Button> </Button>
<div className={styles.label}>Профиль в сайдбаре</div> <div className={styles.label}>Профиль в сайдбаре</div>

View file

@ -1,4 +1,4 @@
import React, { VFC } from 'react'; import { VFC } from 'react';
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes'; import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings'; import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
@ -8,7 +8,9 @@ import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper'; import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
import { DialogComponentProps } from '~/types/modal'; import { DialogComponentProps } from '~/types/modal';
interface ProfileSidebarProps extends DialogComponentProps {} interface ProfileSidebarProps extends DialogComponentProps {
page: string;
}
const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => { const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => {
return ( return (

View file

@ -126,7 +126,7 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
return ( return (
<div className={styles.wrap} ref={wrapper}> <div className={styles.wrap} ref={wrapper}>
<TagWrapper title={input || placeholder} has_input={true} feature={feature}> <TagWrapper title={input || placeholder} hasInput={true} feature={feature}>
<input <input
type="text" type="text"
value={input} value={input}

View file

@ -9,8 +9,8 @@ import { separateTags } from '~/utils/tag';
type IProps = HTMLAttributes<HTMLDivElement> & { type IProps = HTMLAttributes<HTMLDivElement> & {
tags: Partial<ITag>[]; tags: Partial<ITag>[];
is_deletable?: boolean; deletable?: boolean;
is_editable?: boolean; editable?: boolean;
onTagsChange?: (tags: string[]) => void; onTagsChange?: (tags: string[]) => void;
onTagClick?: (tag: Partial<ITag>) => void; onTagClick?: (tag: Partial<ITag>) => void;
onTagDelete?: (id: ITag['ID']) => void; onTagDelete?: (id: ITag['ID']) => void;
@ -18,8 +18,8 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
export const Tags: FC<IProps> = ({ export const Tags: FC<IProps> = ({
tags, tags,
is_deletable, deletable,
is_editable, editable,
onTagsChange, onTagsChange,
onTagClick, onTagClick,
onTagDelete, onTagDelete,
@ -78,7 +78,7 @@ export const Tags: FC<IProps> = ({
key={tag.title} key={tag.title}
tag={tag} tag={tag}
onClick={onTagClick} onClick={onTagClick}
is_deletable={is_deletable} deletable={deletable}
onDelete={onTagDelete} onDelete={onTagDelete}
/> />
))} ))}
@ -88,16 +88,16 @@ export const Tags: FC<IProps> = ({
key={tag.title} key={tag.title}
tag={tag} tag={tag}
onClick={onTagClick} onClick={onTagClick}
is_deletable={is_deletable} deletable={deletable}
onDelete={onTagDelete} onDelete={onTagDelete}
/> />
))} ))}
{data.map(title => ( {data.map(title => (
<Tag key={title} tag={{ title }} is_editing /> <Tag key={title} tag={{ title }} editing />
))} ))}
{is_editable && ( {editable && (
<TagInput <TagInput
onAppend={onAppendTag} onAppend={onAppendTag}
onClearTag={onClearTag} onClearTag={onClearTag}

View file

@ -1,31 +0,0 @@
import { useCallback, useMemo } from 'react';
import { useRouter } from 'next/router';
/** use this to preserve scrolling and handle back button behaviour on
* opening modal
*
* this will replace url with ?modal=modalName, next you should
* show modal for that name and pass params to it
*/
export const useModalRouting = () => {
const router = useRouter();
const openModal = useCallback(
(modalName: string) => {
const [path] = router.asPath.split('?');
void router.push(path + '?modal=' + modalName, path + '?modal=' + modalName, {
shallow: true,
scroll: false,
});
},
[router]
);
const currentModal = useMemo(() => router.query.modal, [router]);
console.log(currentModal);
return { openModal };
};

View file

@ -1,5 +1,9 @@
@import "src/styles/variables"; @import "src/styles/variables";
.container {
width: 100vw;
}
.menu { .menu {
flex: 1 0; flex: 1 0;
padding: $gap; padding: $gap;

View file

@ -18,6 +18,7 @@ import { AuthProvider } from '~/utils/providers/AuthProvider';
import { MetadataProvider } from '~/utils/providers/MetadataProvider'; import { MetadataProvider } from '~/utils/providers/MetadataProvider';
import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider'; import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider';
import { SearchProvider } from '~/utils/providers/SearchProvider'; import { SearchProvider } from '~/utils/providers/SearchProvider';
import { SidebarProvider } from '~/utils/providers/SidebarProvider';
import { ToastProvider } from '~/utils/providers/ToastProvider'; import { ToastProvider } from '~/utils/providers/ToastProvider';
import '~/styles/main.scss'; import '~/styles/main.scss';
@ -41,22 +42,24 @@ export default class MyApp extends App {
<AudioPlayerProvider> <AudioPlayerProvider>
<MetadataProvider> <MetadataProvider>
<AuthProvider> <AuthProvider>
<Head> <SidebarProvider>
<meta <Head>
name="viewport" <meta
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport"
/> content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=0"
/>
{!!canonicalURL && <link rel="canonical" href={canonicalURL} />} {!!canonicalURL && <link rel="canonical" href={canonicalURL} />}
</Head> </Head>
<MainLayout> <MainLayout>
<ToastProvider /> <ToastProvider />
<Modal /> <Modal />
<Sprites /> <Sprites />
<Component {...pageProps} /> <Component {...pageProps} />
</MainLayout> </MainLayout>
<BottomContainer /> <BottomContainer />
</SidebarProvider>
</AuthProvider> </AuthProvider>
</MetadataProvider> </MetadataProvider>
</AudioPlayerProvider> </AudioPlayerProvider>

View file

@ -0,0 +1,78 @@
import { Context, createContext, createElement, FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react';
import { useRouter } from 'next/router';
import { has } from 'ramda';
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
import { sidebarComponents, SidebarName } from '~/constants/sidebar';
import { DialogComponentProps } from '~/types/modal';
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;
type Name = keyof typeof sidebarComponents;
// TODO: use it to store props for sidebar
type Props<T extends Name> = typeof sidebarComponents[T] extends FunctionComponent<infer U>
? U extends DialogComponentProps ? Omit<U, 'onRequestClose'> : U
: {};
const SidebarContext = createContext({
current: undefined as SidebarName | undefined,
open: <T extends Name>(name: T) => {},
close: () => {},
});
export const SidebarProvider = <T extends Name>({ children }: PropsWithChildren<{}>) => {
const router = useRouter();
const current = useMemo(() => {
const val = router.query.sidebar as SidebarName | undefined
return val && has(val, sidebarComponents) ? val : undefined;
}, [router]);
const open = useCallback(
<T extends Name>(name: T) => {
const [path] = router.asPath.split('?');
void router.push(path + '?sidebar=' + name, path + '?sidebar=' + name, {
shallow: true,
scroll: false,
});
},
[router]
);
const close = useCallback(
() => {
const [path] = router.asPath.split('?');
console.log('trying to close');
void router.replace(path, path, {
shallow: true,
scroll: false,
});
},
[router]
);
const value = useMemo<ContextValue>(() => ({
current,
open,
close,
}), [current, open, close]);
return (
<SidebarContext.Provider value={value}>
{children}
{current &&
<ModalWrapper onOverlayClick={close}>
{createElement(
sidebarComponents[current],
{ onRequestClose: close } as any
)}
</ModalWrapper>
}
</SidebarContext.Provider>
);
}
export const useSidebar = () => useContext(SidebarContext);

File diff suppressed because one or more lines are too long