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:
parent
8a4709103b
commit
8a71d3d462
18 changed files with 166 additions and 108 deletions
|
@ -1,6 +1,6 @@
|
|||
#NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||
#NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
#NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@
|
|||
/build
|
||||
/.next
|
||||
/.vscode
|
||||
/.history
|
|
@ -1,11 +1,9 @@
|
|||
import React, { FC, useState } from 'react';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
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 styles from './styles.module.scss';
|
||||
|
@ -14,7 +12,6 @@ interface IProps {}
|
|||
|
||||
const BorisUIDemo: FC<IProps> = () => {
|
||||
const [text, setText] = useState('');
|
||||
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar);
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
|
@ -25,9 +22,6 @@ const BorisUIDemo: FC<IProps> = () => {
|
|||
разработке
|
||||
</p>
|
||||
|
||||
<h2>Тестовые фичи</h2>
|
||||
<Button onClick={() => openProfileSidebar({})}>Профиль в сайдбаре</Button>
|
||||
|
||||
<h2>Инпуты</h2>
|
||||
|
||||
<form autoComplete="off">
|
||||
|
|
|
@ -17,11 +17,11 @@ const NodeTags: FC<IProps> = memo(
|
|||
return (
|
||||
<Tags
|
||||
tags={tags}
|
||||
is_editable={is_editable}
|
||||
editable={is_editable}
|
||||
onTagsChange={onChange}
|
||||
onTagClick={onTagClick}
|
||||
onTagDelete={onTagDelete}
|
||||
is_deletable={is_deletable}
|
||||
deletable={is_deletable}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
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 };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { VFC } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
|
@ -8,7 +8,6 @@ import { Button } from '~/components/input/Button';
|
|||
import { VerticalMenu } from '~/components/menu/VerticalMenu';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead';
|
||||
import { ProfileStats } from '~/containers/profile/ProfileStats';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
|
@ -18,8 +17,6 @@ const SettingsMenu: VFC<SettingsMenuProps> = () => (
|
|||
<Group>
|
||||
<ProfileSidebarHead />
|
||||
|
||||
<br />
|
||||
|
||||
<Group>
|
||||
<VerticalMenu className={styles.menu}>
|
||||
<Link href={URLS.SETTINGS.BASE} passHref>
|
||||
|
@ -35,10 +32,6 @@ const SettingsMenu: VFC<SettingsMenuProps> = () => (
|
|||
</Link>
|
||||
</VerticalMenu>
|
||||
|
||||
<div className={styles.stats}>
|
||||
<ProfileStats />
|
||||
</div>
|
||||
|
||||
<Group horizontal>
|
||||
<Filler />
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ interface IProps {
|
|||
tag: Partial<ITag>;
|
||||
size?: 'normal' | 'big';
|
||||
|
||||
is_deletable?: boolean;
|
||||
is_hoverable?: boolean;
|
||||
is_editing?: boolean;
|
||||
deletable?: boolean;
|
||||
hoverable?: boolean;
|
||||
editing?: boolean;
|
||||
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onClick?: (tag: Partial<ITag>) => void;
|
||||
|
@ -24,9 +24,9 @@ interface IProps {
|
|||
|
||||
const Tag: FC<IProps> = ({
|
||||
tag,
|
||||
is_deletable,
|
||||
is_hoverable,
|
||||
is_editing,
|
||||
deletable: deletable,
|
||||
hoverable: hoverable,
|
||||
editing: editing,
|
||||
size = 'normal',
|
||||
onClick,
|
||||
onDelete,
|
||||
|
@ -48,9 +48,9 @@ const Tag: FC<IProps> = ({
|
|||
<TagWrapper
|
||||
feature={getTagFeature(tag)}
|
||||
size={size}
|
||||
is_deletable={is_deletable}
|
||||
is_hoverable={is_hoverable}
|
||||
is_editing={is_editing}
|
||||
deletable={deletable}
|
||||
hoverable={hoverable}
|
||||
editing={editing}
|
||||
onClick={onClick && onClickHandler}
|
||||
onDelete={onDeleteHandler}
|
||||
title={tag.title}
|
||||
|
|
|
@ -9,10 +9,10 @@ import styles from './styles.module.scss';
|
|||
interface IProps {
|
||||
feature?: string;
|
||||
size?: string;
|
||||
is_deletable?: boolean;
|
||||
is_hoverable?: boolean;
|
||||
is_editing?: boolean;
|
||||
has_input?: boolean;
|
||||
deletable?: boolean;
|
||||
hoverable?: boolean;
|
||||
editing?: boolean;
|
||||
hasInput?: boolean;
|
||||
onClick?: () => void;
|
||||
onDelete?: () => void;
|
||||
title?: string;
|
||||
|
@ -22,15 +22,15 @@ const TagWrapper: FC<IProps> = ({
|
|||
children,
|
||||
feature,
|
||||
size,
|
||||
is_deletable,
|
||||
is_hoverable,
|
||||
is_editing,
|
||||
has_input,
|
||||
deletable,
|
||||
hoverable,
|
||||
editing,
|
||||
hasInput,
|
||||
onClick,
|
||||
onDelete,
|
||||
title = '',
|
||||
}) => {
|
||||
const deletable = is_deletable && !is_editing && !has_input;
|
||||
const canBeDeleted = deletable && !editing && !hasInput;
|
||||
const onDeletePress = useCallback(
|
||||
event => {
|
||||
if (!onDelete) {
|
||||
|
@ -46,10 +46,10 @@ const TagWrapper: FC<IProps> = ({
|
|||
return (
|
||||
<div
|
||||
className={classNames(styles.tag, feature, size, {
|
||||
is_hoverable,
|
||||
is_editing,
|
||||
deletable,
|
||||
input: has_input,
|
||||
is_hoverable: hoverable,
|
||||
is_editing: editing,
|
||||
deletable: canBeDeleted,
|
||||
input: hasInput,
|
||||
clickable: onClick,
|
||||
})}
|
||||
onClick={onClick}
|
||||
|
|
11
src/constants/sidebar/index.ts
Normal file
11
src/constants/sidebar/index.ts
Normal 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
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
import { FC } from 'react';
|
||||
import { FC, useCallback } from 'react';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { SidebarName } from '~/constants/sidebar';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||
import { useSidebar } from '~/utils/providers/SidebarProvider';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
export interface BorisSuperpowersProps {}
|
||||
|
||||
const BorisSuperpowers: FC<BorisSuperpowersProps> = () => {
|
||||
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar);
|
||||
const { open } = useSidebar();
|
||||
const openProfileSidebar = useCallback(() => {
|
||||
open(SidebarName.Settings);
|
||||
}, [open]);
|
||||
const { push } = useRouter();
|
||||
|
||||
return (
|
||||
|
@ -21,7 +24,7 @@ const BorisSuperpowers: FC<BorisSuperpowersProps> = () => {
|
|||
<h2>Штучи, находящиеся в разработке</h2>
|
||||
|
||||
<div className={styles.grid}>
|
||||
<Button size="mini" onClick={() => openProfileSidebar({})}>
|
||||
<Button size="mini" onClick={() => openProfileSidebar()}>
|
||||
Открыть
|
||||
</Button>
|
||||
<div className={styles.label}>Профиль в сайдбаре</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { VFC } from 'react';
|
||||
|
||||
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
|
||||
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
|
||||
|
@ -8,7 +8,9 @@ import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
|
|||
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
interface ProfileSidebarProps extends DialogComponentProps {}
|
||||
interface ProfileSidebarProps extends DialogComponentProps {
|
||||
page: string;
|
||||
}
|
||||
|
||||
const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => {
|
||||
return (
|
||||
|
|
|
@ -126,7 +126,7 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
|||
|
||||
return (
|
||||
<div className={styles.wrap} ref={wrapper}>
|
||||
<TagWrapper title={input || placeholder} has_input={true} feature={feature}>
|
||||
<TagWrapper title={input || placeholder} hasInput={true} feature={feature}>
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
|
|
|
@ -9,8 +9,8 @@ import { separateTags } from '~/utils/tag';
|
|||
|
||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||
tags: Partial<ITag>[];
|
||||
is_deletable?: boolean;
|
||||
is_editable?: boolean;
|
||||
deletable?: boolean;
|
||||
editable?: boolean;
|
||||
onTagsChange?: (tags: string[]) => void;
|
||||
onTagClick?: (tag: Partial<ITag>) => void;
|
||||
onTagDelete?: (id: ITag['ID']) => void;
|
||||
|
@ -18,8 +18,8 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
|
|||
|
||||
export const Tags: FC<IProps> = ({
|
||||
tags,
|
||||
is_deletable,
|
||||
is_editable,
|
||||
deletable,
|
||||
editable,
|
||||
onTagsChange,
|
||||
onTagClick,
|
||||
onTagDelete,
|
||||
|
@ -78,7 +78,7 @@ export const Tags: FC<IProps> = ({
|
|||
key={tag.title}
|
||||
tag={tag}
|
||||
onClick={onTagClick}
|
||||
is_deletable={is_deletable}
|
||||
deletable={deletable}
|
||||
onDelete={onTagDelete}
|
||||
/>
|
||||
))}
|
||||
|
@ -88,16 +88,16 @@ export const Tags: FC<IProps> = ({
|
|||
key={tag.title}
|
||||
tag={tag}
|
||||
onClick={onTagClick}
|
||||
is_deletable={is_deletable}
|
||||
deletable={deletable}
|
||||
onDelete={onTagDelete}
|
||||
/>
|
||||
))}
|
||||
|
||||
{data.map(title => (
|
||||
<Tag key={title} tag={{ title }} is_editing />
|
||||
<Tag key={title} tag={{ title }} editing />
|
||||
))}
|
||||
|
||||
{is_editable && (
|
||||
{editable && (
|
||||
<TagInput
|
||||
onAppend={onAppendTag}
|
||||
onClearTag={onClearTag}
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -1,5 +1,9 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.container {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.menu {
|
||||
flex: 1 0;
|
||||
padding: $gap;
|
||||
|
|
|
@ -18,6 +18,7 @@ import { AuthProvider } from '~/utils/providers/AuthProvider';
|
|||
import { MetadataProvider } from '~/utils/providers/MetadataProvider';
|
||||
import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider';
|
||||
import { SearchProvider } from '~/utils/providers/SearchProvider';
|
||||
import { SidebarProvider } from '~/utils/providers/SidebarProvider';
|
||||
import { ToastProvider } from '~/utils/providers/ToastProvider';
|
||||
|
||||
import '~/styles/main.scss';
|
||||
|
@ -41,22 +42,24 @@ export default class MyApp extends App {
|
|||
<AudioPlayerProvider>
|
||||
<MetadataProvider>
|
||||
<AuthProvider>
|
||||
<Head>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<SidebarProvider>
|
||||
<Head>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
|
||||
{!!canonicalURL && <link rel="canonical" href={canonicalURL} />}
|
||||
</Head>
|
||||
{!!canonicalURL && <link rel="canonical" href={canonicalURL} />}
|
||||
</Head>
|
||||
|
||||
<MainLayout>
|
||||
<ToastProvider />
|
||||
<Modal />
|
||||
<Sprites />
|
||||
<Component {...pageProps} />
|
||||
</MainLayout>
|
||||
<BottomContainer />
|
||||
<MainLayout>
|
||||
<ToastProvider />
|
||||
<Modal />
|
||||
<Sprites />
|
||||
<Component {...pageProps} />
|
||||
</MainLayout>
|
||||
<BottomContainer />
|
||||
</SidebarProvider>
|
||||
</AuthProvider>
|
||||
</MetadataProvider>
|
||||
</AudioPlayerProvider>
|
||||
|
|
78
src/utils/providers/SidebarProvider.tsx
Normal file
78
src/utils/providers/SidebarProvider.tsx
Normal 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
Loading…
Add table
Add a link
Reference in a new issue