Start work on tab layout
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import type React from 'react';
|
||||
|
||||
import type { Link } from 'type-route';
|
||||
|
||||
export interface TabProps {
|
||||
link?: Link;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export const Tab = ({ link, icon, title, active }: TabProps): JSX.Element => {
|
||||
return (
|
||||
<div className="group relative flex-grow">
|
||||
<div
|
||||
className={`peer flex cursor-pointer select-none py-1 dark:text-white ${
|
||||
active
|
||||
? 'z-10 -mr-1 rounded-t-lg bg-gray-100 dark:bg-primaryDark'
|
||||
: 'dark:bg-secondaryDark'
|
||||
}`}
|
||||
{...(link && link)}
|
||||
>
|
||||
<div
|
||||
className={`w-full px-2 ${
|
||||
active ? '' : 'border-l group-first:border-l-0 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="my-auto">{icon}</div>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -19,6 +19,7 @@ export const IconButton = ({
|
||||
nested,
|
||||
active,
|
||||
disabled,
|
||||
className,
|
||||
...props
|
||||
}: IconButtonProps): JSX.Element => {
|
||||
return (
|
||||
@@ -39,7 +40,7 @@ export const IconButton = ({
|
||||
disabled
|
||||
? 'cursor-not-allowed text-gray-400 dark:text-gray-700'
|
||||
: ''
|
||||
}`}
|
||||
} ${className ?? ''}`}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
||||
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
||||
import { VscExtensions } from 'react-icons/vsc';
|
||||
|
||||
import { NavLinkButton } from '@components/layout/Sidebar/NavLinkButton';
|
||||
import { routes, useRoute } from '@core/router';
|
||||
import { toggleMobileNav } from '@core/slices/appSlice';
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
|
||||
export interface ButtonNavProps {
|
||||
toggleSettingsOpen: () => void;
|
||||
}
|
||||
|
||||
export const ButtonNav = ({
|
||||
toggleSettingsOpen,
|
||||
}: ButtonNavProps): JSX.Element => {
|
||||
const route = useRoute();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<div className="z-30 flex justify-between border-t border-gray-300 bg-white px-6 py-2 dark:border-gray-600 dark:bg-primaryDark">
|
||||
<div
|
||||
onClick={(): void => {
|
||||
dispatch(toggleMobileNav());
|
||||
}}
|
||||
>
|
||||
<NavLinkButton
|
||||
active={route.name === 'messages'}
|
||||
link={routes.messages().link}
|
||||
>
|
||||
<FiMessageCircle className="h-5 w-5" />
|
||||
</NavLinkButton>
|
||||
</div>
|
||||
<div
|
||||
onClick={(): void => {
|
||||
dispatch(toggleMobileNav());
|
||||
}}
|
||||
>
|
||||
<NavLinkButton
|
||||
active={route.name === 'nodes'}
|
||||
link={routes.nodes().link}
|
||||
>
|
||||
<RiMindMap className="h-5 w-5" />
|
||||
</NavLinkButton>
|
||||
</div>
|
||||
<div
|
||||
onClick={(): void => {
|
||||
dispatch(toggleMobileNav());
|
||||
}}
|
||||
>
|
||||
<NavLinkButton active={route.name === 'map'} link={routes.map().link}>
|
||||
<RiRoadMapLine className="h-5 w-5" />
|
||||
</NavLinkButton>
|
||||
</div>
|
||||
<div
|
||||
onClick={(): void => {
|
||||
dispatch(toggleMobileNav());
|
||||
}}
|
||||
>
|
||||
<NavLinkButton
|
||||
active={route.name === 'extensions'}
|
||||
link={routes.extensions().link}
|
||||
>
|
||||
<VscExtensions className="h-5 w-5" />
|
||||
</NavLinkButton>
|
||||
</div>
|
||||
<NavLinkButton
|
||||
action={(): void => {
|
||||
toggleSettingsOpen();
|
||||
}}
|
||||
>
|
||||
<FiSettings className="h-5 w-5" />
|
||||
</NavLinkButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
import type { Link } from 'type-route';
|
||||
|
||||
export interface NavLinkButtonProps {
|
||||
link?: Link;
|
||||
active?: boolean;
|
||||
action?: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NavLinkButton = ({
|
||||
link,
|
||||
active,
|
||||
action,
|
||||
children,
|
||||
}: NavLinkButtonProps): JSX.Element => {
|
||||
return (
|
||||
<m.div
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
animate={active ? 'selected' : 'deselected'}
|
||||
initial={{ borderColor: '#1C1D23' }}
|
||||
variants={{
|
||||
selected: { borderColor: '#67ea94' },
|
||||
deselected: { borderColor: '#1C1D23' },
|
||||
}}
|
||||
className="cursor-pointer rounded-full border-2 p-3 hover:bg-opacity-80 hover:shadow-md dark:bg-secondaryDark dark:text-white"
|
||||
onClick={(): void => {
|
||||
action && action();
|
||||
}}
|
||||
{...(link && link)}
|
||||
>
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,19 @@
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ButtonNav } from '@components/layout/Sidebar/ButtonNav';
|
||||
import { Settings } from '@components/layout/Sidebar/Settings/Index';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
export interface SidebarProps {
|
||||
children: React.ReactNode;
|
||||
setSettingsOpen: (settingsOpen: boolean) => void;
|
||||
settingsOpen: boolean;
|
||||
}
|
||||
|
||||
export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
export const Sidebar = ({
|
||||
settingsOpen,
|
||||
setSettingsOpen,
|
||||
children,
|
||||
}: SidebarProps): JSX.Element => {
|
||||
const appState = useAppSelector((state) => state.app);
|
||||
|
||||
return (
|
||||
@@ -25,11 +27,6 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
||||
<div className="absolute h-full w-full">{children}</div>
|
||||
<Settings open={settingsOpen} setOpen={setSettingsOpen} />
|
||||
</div>
|
||||
<ButtonNav
|
||||
toggleSettingsOpen={(): void => {
|
||||
setSettingsOpen(!settingsOpen);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
||||
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
||||
import { VscExtensions } from 'react-icons/vsc';
|
||||
|
||||
import { routes, useRoute } from '@app/core/router';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Sidebar } from '@components/layout/Sidebar';
|
||||
|
||||
import { ErrorFallback } from '../ErrorFallback';
|
||||
import { Tab } from '../Tab';
|
||||
|
||||
export interface LayoutProps {
|
||||
title: string;
|
||||
@@ -20,10 +26,14 @@ export const Layout = ({
|
||||
sidebarContents,
|
||||
children,
|
||||
}: LayoutProps): JSX.Element => {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
return (
|
||||
<div className="relative flex w-full bg-gray-100 dark:bg-secondaryDark md:overflow-hidden md:shadow-xl">
|
||||
<div className="flex flex-grow">
|
||||
<Sidebar>
|
||||
<Sidebar settingsOpen={settingsOpen} setSettingsOpen={setSettingsOpen}>
|
||||
<div className="flex gap-2 border-b border-gray-300 p-2 dark:border-gray-600">
|
||||
<IconButton icon={icon} />
|
||||
<div className="my-auto text-lg font-medium dark:text-white">
|
||||
@@ -34,8 +44,45 @@ export const Layout = ({
|
||||
</Sidebar>
|
||||
</div>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<div className="flex h-full w-full bg-gray-300 dark:bg-secondaryDark">
|
||||
{children}
|
||||
<div className="flex h-full w-full flex-col bg-gray-300 dark:bg-secondaryDark">
|
||||
<div className="flex w-full border-b border-gray-300 pr-2 pt-1 dark:border-gray-600">
|
||||
<div className="mx-1">
|
||||
<IconButton
|
||||
className="rounded-b-none"
|
||||
onClick={(): void => {
|
||||
setSettingsOpen(!settingsOpen);
|
||||
}}
|
||||
nested
|
||||
active={settingsOpen}
|
||||
icon={<FiSettings />}
|
||||
/>
|
||||
</div>
|
||||
<Tab
|
||||
title="Messages"
|
||||
icon={<FiMessageCircle />}
|
||||
link={routes.messages().link}
|
||||
active={route.name === 'messages'}
|
||||
/>
|
||||
<Tab
|
||||
title="Nodes"
|
||||
icon={<RiMindMap />}
|
||||
link={routes.nodes().link}
|
||||
active={route.name === 'nodes'}
|
||||
/>
|
||||
<Tab
|
||||
title="Map"
|
||||
icon={<RiRoadMapLine />}
|
||||
link={routes.map().link}
|
||||
active={route.name === 'map'}
|
||||
/>
|
||||
<Tab
|
||||
title="Extensions"
|
||||
icon={<VscExtensions />}
|
||||
link={routes.extensions().link}
|
||||
active={route.name === 'extensions'}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-grow">{children}</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user