mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
added sidebar navigation
This commit is contained in:
parent
1bb08f72e6
commit
a03f80259d
6 changed files with 150 additions and 70 deletions
|
@ -1,11 +1,21 @@
|
||||||
import React, { createContext, FC, PropsWithChildren, useCallback, useContext, useMemo, useState } from 'react';
|
import React, {
|
||||||
|
createContext,
|
||||||
|
FC,
|
||||||
|
PropsWithChildren,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { isNil } from '~/utils/ramda';
|
import { isNil } from "~/utils/ramda";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface SidebarStackProps extends PropsWithChildren<{}> {
|
interface SidebarStackProps extends PropsWithChildren<{}> {
|
||||||
initialTab?: number;
|
tab?: number;
|
||||||
|
onTabChange?: (index?: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarStackContextValue {
|
interface SidebarStackContextValue {
|
||||||
|
@ -38,12 +48,31 @@ const SidebarCards: FC = ({ children }) => {
|
||||||
return <div className={styles.card}>{nonEmptyChildren[activeTab]}</div>;
|
return <div className={styles.card}>{nonEmptyChildren[activeTab]}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarStack = function({ children, initialTab }: SidebarStackProps) {
|
const SidebarStack = function({
|
||||||
const [activeTab, setActiveTab] = useState<number | undefined>(initialTab);
|
children,
|
||||||
const closeAllTabs = useCallback(() => setActiveTab(undefined), []);
|
tab,
|
||||||
|
onTabChange,
|
||||||
|
}: SidebarStackProps) {
|
||||||
|
const [activeTab, setActiveTab] = useState<number | undefined>(tab);
|
||||||
|
|
||||||
|
const closeAllTabs = useCallback(() => {
|
||||||
|
setActiveTab(undefined);
|
||||||
|
onTabChange?.(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChangeTab = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
onTabChange?.(index);
|
||||||
|
setActiveTab(index);
|
||||||
|
},
|
||||||
|
[onTabChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => setActiveTab(tab), [tab]);
|
||||||
return (
|
return (
|
||||||
<SidebarStackContext.Provider value={{ activeTab, setActiveTab, closeAllTabs }}>
|
<SidebarStackContext.Provider
|
||||||
|
value={{ activeTab, setActiveTab: onChangeTab, closeAllTabs }}
|
||||||
|
>
|
||||||
<div className={styles.stack}>{children}</div>
|
<div className={styles.stack}>{children}</div>
|
||||||
</SidebarStackContext.Provider>
|
</SidebarStackContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { FC, useCallback } 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 { SidebarName } from '~/constants/sidebar';
|
import { SidebarName } from "~/constants/sidebar";
|
||||||
import { URLS } from '~/constants/urls';
|
import { URLS } from "~/constants/urls";
|
||||||
import { useSidebar } from '~/utils/providers/SidebarProvider';
|
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 { open } = useSidebar();
|
const { open } = useSidebar();
|
||||||
const openProfileSidebar = useCallback(() => {
|
const openProfileSidebar = useCallback(() => {
|
||||||
open(SidebarName.Settings);
|
open(SidebarName.Settings, { page: "profile" });
|
||||||
}, [open]);
|
}, [open]);
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,48 @@
|
||||||
import { VFC } from 'react';
|
import { useCallback, useMemo, VFC } from "react";
|
||||||
|
|
||||||
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
|
import { isNil } from "ramda";
|
||||||
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
|
|
||||||
import { SidebarStack } from '~/components/sidebar/SidebarStack';
|
|
||||||
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
|
|
||||||
import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
|
|
||||||
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
|
|
||||||
import { DialogComponentProps } from '~/types/modal';
|
|
||||||
|
|
||||||
interface ProfileSidebarProps extends DialogComponentProps {
|
import { ProfileSidebarNotes } from "~/components/profile/ProfileSidebarNotes";
|
||||||
page: string;
|
import { ProfileSidebarSettings } from "~/components/profile/ProfileSidebarSettings";
|
||||||
|
import { SidebarStack } from "~/components/sidebar/SidebarStack";
|
||||||
|
import { SidebarStackCard } from "~/components/sidebar/SidebarStackCard";
|
||||||
|
import { SidebarName } from "~/constants/sidebar";
|
||||||
|
import { ProfileSidebarMenu } from "~/containers/profile/ProfileSidebarMenu";
|
||||||
|
import { SidebarWrapper } from "~/containers/sidebars/SidebarWrapper";
|
||||||
|
import { SidebarComponentProps } from "~/types/sidebar";
|
||||||
|
import { useSidebar } from "~/utils/providers/SidebarProvider";
|
||||||
|
|
||||||
|
type TabName = "profile" | "bookmarks";
|
||||||
|
interface ProfileSidebarProps extends SidebarComponentProps {
|
||||||
|
page?: TabName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => {
|
const tabs: TabName[] = ["profile", "bookmarks"];
|
||||||
|
|
||||||
|
const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose, page }) => {
|
||||||
|
const initialTab = useMemo(
|
||||||
|
() => (page ? Math.min(tabs.indexOf(page), 0) : undefined),
|
||||||
|
[page],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { open } = useSidebar();
|
||||||
|
|
||||||
|
const onTabChange = useCallback(
|
||||||
|
(val: number | undefined) => {
|
||||||
|
console.log({ val });
|
||||||
|
open(SidebarName.Settings, { page: !isNil(val) ? tabs[val] : undefined });
|
||||||
|
},
|
||||||
|
[open],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarWrapper onClose={onRequestClose}>
|
<SidebarWrapper onClose={onRequestClose}>
|
||||||
<SidebarStack>
|
<SidebarStack tab={initialTab} onTabChange={onTabChange}>
|
||||||
<SidebarStackCard headerFeature="close" title="Профиль" onBackPress={onRequestClose}>
|
<SidebarStackCard
|
||||||
|
headerFeature="close"
|
||||||
|
title="Профиль"
|
||||||
|
onBackPress={onRequestClose}
|
||||||
|
>
|
||||||
<ProfileSidebarMenu onClose={onRequestClose} />
|
<ProfileSidebarMenu onClose={onRequestClose} />
|
||||||
</SidebarStackCard>
|
</SidebarStackCard>
|
||||||
|
|
||||||
|
|
3
src/types/sidebar/index.ts
Normal file
3
src/types/sidebar/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export interface SidebarComponentProps {
|
||||||
|
onRequestClose: () => void;
|
||||||
|
}
|
|
@ -1,78 +1,100 @@
|
||||||
import { Context, createContext, createElement, FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react';
|
import {
|
||||||
|
Context,
|
||||||
|
createContext,
|
||||||
|
createElement,
|
||||||
|
FunctionComponent,
|
||||||
|
PropsWithChildren,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from "next/router";
|
||||||
import { has } from 'ramda';
|
import { has, omit } from "ramda";
|
||||||
|
|
||||||
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
import { ModalWrapper } from "~/components/dialogs/ModalWrapper";
|
||||||
import { sidebarComponents, SidebarName } from '~/constants/sidebar';
|
import { sidebarComponents, SidebarName } from "~/constants/sidebar";
|
||||||
import { DialogComponentProps } from '~/types/modal';
|
import { DialogComponentProps } from "~/types/modal";
|
||||||
|
import { SidebarComponentProps } from "~/types/sidebar";
|
||||||
|
|
||||||
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;
|
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;
|
||||||
type Name = keyof typeof sidebarComponents;
|
type Name = keyof typeof sidebarComponents;
|
||||||
|
|
||||||
// TODO: use it to store props for sidebar
|
// TODO: use it to store props for sidebar
|
||||||
type Props<T extends Name> = typeof sidebarComponents[T] extends FunctionComponent<infer U>
|
type Props<
|
||||||
? U extends DialogComponentProps ? Omit<U, 'onRequestClose'> : U
|
T extends Name
|
||||||
|
> = typeof sidebarComponents[T] extends FunctionComponent<infer U>
|
||||||
|
? U extends object
|
||||||
|
? U extends SidebarComponentProps
|
||||||
|
? Omit<U, "onRequestClose">
|
||||||
|
: U
|
||||||
|
: U
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const SidebarContext = createContext({
|
const SidebarContext = createContext({
|
||||||
current: undefined as SidebarName | undefined,
|
current: undefined as SidebarName | undefined,
|
||||||
open: <T extends Name>(name: T) => {},
|
open: <T extends Name>(name: T, props: Props<T>) => {},
|
||||||
close: () => {},
|
close: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SidebarProvider = <T extends Name>({ children }: PropsWithChildren<{}>) => {
|
export const SidebarProvider = <T extends Name>({
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<{}>) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const current = useMemo(() => {
|
const current = useMemo(() => {
|
||||||
const val = router.query.sidebar as SidebarName | undefined
|
const val = router.query.sidebar as SidebarName | undefined;
|
||||||
|
|
||||||
return val && has(val, sidebarComponents) ? val : undefined;
|
return val && has(val, sidebarComponents) ? val : undefined;
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const open = useCallback(
|
const open = useCallback(
|
||||||
<T extends Name>(name: T) => {
|
<T extends Name>(name: T, props: Props<T>) => {
|
||||||
const [path] = router.asPath.split('?');
|
const [path] = router.asPath.split("?");
|
||||||
void router.push(path + '?sidebar=' + name, path + '?sidebar=' + name, {
|
const query = Object.entries(props as {})
|
||||||
|
.filter(([, val]) => val)
|
||||||
|
.map(([name, val]) => `${name}=${val}`)
|
||||||
|
.join("&");
|
||||||
|
const url = path + "?sidebar=" + name + (query && `&${query}`);
|
||||||
|
|
||||||
|
void router.push(url, url, {
|
||||||
shallow: true,
|
shallow: true,
|
||||||
scroll: false,
|
scroll: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[router]
|
[router],
|
||||||
);
|
);
|
||||||
|
|
||||||
const close = useCallback(
|
const close = useCallback(() => {
|
||||||
() => {
|
const [path] = router.asPath.split("?");
|
||||||
const [path] = router.asPath.split('?');
|
|
||||||
|
|
||||||
console.log('trying to close');
|
void router.replace(path, path, {
|
||||||
|
shallow: true,
|
||||||
|
scroll: false,
|
||||||
|
});
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
void router.replace(path, path, {
|
const value = useMemo<ContextValue>(
|
||||||
shallow: true,
|
() => ({
|
||||||
scroll: false,
|
current,
|
||||||
});
|
open,
|
||||||
},
|
close,
|
||||||
[router]
|
}),
|
||||||
|
[current, open, close],
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = useMemo<ContextValue>(() => ({
|
|
||||||
current,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
}), [current, open, close]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarContext.Provider value={value}>
|
<SidebarContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
{current &&
|
{current && (
|
||||||
<ModalWrapper onOverlayClick={close}>
|
<ModalWrapper onOverlayClick={close}>
|
||||||
{createElement(
|
{createElement(sidebarComponents[current], {
|
||||||
sidebarComponents[current],
|
onRequestClose: close,
|
||||||
{ onRequestClose: close } as any
|
...omit(["sidebar"], router.query),
|
||||||
)}
|
} as any)}
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
}
|
)}
|
||||||
</SidebarContext.Provider>
|
</SidebarContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useSidebar = () => useContext(SidebarContext);
|
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