Browse Source

Start work on tab layout

pull/21/head
Sacha Weatherstone 4 years ago
parent
commit
9ad48da3ec
  1. 36
      src/components/Tab.tsx
  2. 3
      src/components/generic/button/IconButton.tsx
  3. 78
      src/components/layout/Sidebar/ButtonNav.tsx
  4. 38
      src/components/layout/Sidebar/NavLinkButton.tsx
  5. 17
      src/components/layout/Sidebar/index.tsx
  6. 53
      src/components/layout/index.tsx

36
src/components/Tab.tsx

@ -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>
);
};

3
src/components/generic/button/IconButton.tsx

@ -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}

78
src/components/layout/Sidebar/ButtonNav.tsx

@ -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>
);
};

38
src/components/layout/Sidebar/NavLinkButton.tsx

@ -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>
);
};

17
src/components/layout/Sidebar/index.tsx

@ -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>
);

53
src/components/layout/index.tsx

@ -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>

Loading…
Cancel
Save