Skip to content

Commit

Permalink
[UI] Add options sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanleung committed Mar 2, 2024
1 parent 5d9514f commit c29eb65
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 86 deletions.
48 changes: 48 additions & 0 deletions src/frontend/components/buttons/sidebar-menu-navigation-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Styled from 'styled-components';
import { Link, useLocation } from 'react-router-dom';

interface ButtonProps {
active: 'active' | '';
}

interface MainNavButtonProps {
text: string;
href: string;
exact: boolean;
}

const SidebarMenuNavigationButton = ({ text, href, exact }: MainNavButtonProps) => {
const path = useLocation();
const active = exact ? path.pathname === href : path.pathname.includes(href);

return (
<MenuNavItem.Wrapper to={href} active={active ? 'active' : ''}>
<MenuNavItem.ButtonText>{text}</MenuNavItem.ButtonText>
</MenuNavItem.Wrapper>
);
};

const MenuNavItem = {
Wrapper: Styled(Link)<ButtonProps>`
display: flex;
flex-direction: column;
padding: 24px 0 24px 12px;
color: ${(props) => (props.active === 'active' ? props.theme.colors.black : props.theme.colors.balance)};
text-decoration: none;
background-color: ${(props) => props.active && props.theme.colors.balance};
&:hover span {
color: ${(props) => !props.active && props.theme.colors.gray};
}
`,
Icon: Styled.img`
display: flex;
width: 30px;
height: 30px;
`,
ButtonText: Styled.span`
font-family: ${(props) => props.theme.fonts.family.primary.regular};
font-size: ${(props) => props.theme.fonts.size.medium};
`,
};

export default SidebarMenuNavigationButton;
84 changes: 84 additions & 0 deletions src/frontend/components/dropdowns/network-selection-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import Styled from 'styled-components';

import { getChainInfoByChainId } from '../../utils/chain';
import { useSDK } from '@metamask/sdk-react';
import { useAIMessagesContext } from '../../contexts';

const NetworkSelectionDropdown = () => {
const { provider, account } = useSDK();
const [dialogueEntries, setDialogueEntries] = useAIMessagesContext();

const handleNetworkChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedChain = e.target.value;

// Check if the default option is selected
if (!selectedChain) {
console.log('No network selected.');
return; // Early return to avoid further execution
}

// Sanity Checks:
if (!account || !provider) {
const errorMessage = `Error: Please connect to MetaMask`;
setDialogueEntries([
...dialogueEntries,
{ question: '', answer: errorMessage, answered: true },
]);
return;
}

try {
const response = await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: selectedChain }],
});
console.log(response);
} catch (error) {
//if switch chain fails then add the chain
try {
const chainInfo = getChainInfoByChainId(selectedChain);
const response = await provider.request({
method: 'wallet_addEthereumChain',
params: [chainInfo],
});
} catch (error) {
console.error('Failed to switch networks:', error);
}
}
};

return (
<Dropdown onChange={handleNetworkChange} value="">
<option value="">Select a network</option>
<option value="0x1">Ethereum</option>
<option value="0xaa36a7">Sepolia</option>
<option value="0xa4b1">Arbitrum</option>
<option value="0x64">Gnosis</option>
</Dropdown>
);
};

const Dropdown = Styled.select`
padding: 8px 10px;
margin: 12px 0 12px 12px;
border-radius: 10px;
background-color: ${(props) => props.theme.colors.core};
color: ${(props) => props.theme.colors.notice};
border: 2px solid ${(props) => props.theme.colors.hunter};
font-family: ${(props) => props.theme.fonts.family.primary.regular};
font-size: ${(props) => props.theme.fonts.size.small};
cursor: pointer;
&:hover {
border: 2px solid ${(props) => props.theme.colors.emerald};
}
option {
background-color: ${(props) => props.theme.colors.core};
color: ${(props) => props.theme.colors.emerald};
}
`;

export default NetworkSelectionDropdown;
35 changes: 25 additions & 10 deletions src/frontend/components/layout/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,45 @@ import BottomBar from './bottom-bar';
// router
import { MainRouter } from '../../router';

import SidebarComponent from './sidebar';
import SidebarToggle from './sidebar-toggle';

export default () => {
return (
<Main.Layout>
<Main.TopWrapper>
<TopBar />
</Main.TopWrapper>
<Main.MainWrapper>
<MainRouter />
</Main.MainWrapper>
<Main.BottomWrapper>
<BottomBar />
</Main.BottomWrapper>
<SidebarComponent />
<SidebarToggle />
<Main.Content>
<Main.TopWrapper>
<TopBar />
</Main.TopWrapper>
<Main.MainWrapper>
<MainRouter />
</Main.MainWrapper>
<Main.BottomWrapper>
<BottomBar />
</Main.BottomWrapper>
</Main.Content>
</Main.Layout>
);
};

const Main = {
Layout: Styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
background: ${(props) => props.theme.colors.core};
`,
Content: Styled.div`
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
height: 100%;
background: ${(props) => props.theme.colors.core};
`,
TopWrapper: Styled.div`
display: flex;
width: 100%;
Expand All @@ -48,6 +62,7 @@ const Main = {
border-radius: 30px;
border: 5px solid ${(props) => props.theme.colors.hunter};
padding: 10px;
overflow: hidden;
`,
BottomWrapper: Styled.div`
display: flex;
Expand Down
46 changes: 46 additions & 0 deletions src/frontend/components/layout/sidebar-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// libs
import React from 'react';
import Styled from 'styled-components';
import { useSidebarContext } from '../../contexts';

export default () => {
const { isOpen, setIsOpen } = useSidebarContext();
return (
<SidebarToggle.Layout>
<SidebarToggle.Carat
onClick={() => {
setIsOpen((isOpen) => !isOpen);
}}
isOpen={isOpen}
/>
</SidebarToggle.Layout>
);
};

type SidebarToggleCarat = {
isOpen: boolean;
};

const SidebarToggle = {
Layout: Styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 100%;
background: ${(props) => props.theme.colors.core};
padding-left: 8px;
`,
Carat: Styled.div<SidebarToggleCarat>`
display: inline-block;
width: 20px;
height: 20px;
border-top: 2px solid ${(props) => props.theme.colors.emerald};
border-left: 2px solid ${(props) => props.theme.colors.emerald};
transform: rotate(${(props) => (props.isOpen ? '-45deg' : '135deg')});
&:hover {
cursor: pointer;
opacity: 0.8;
}
`,
};
78 changes: 78 additions & 0 deletions src/frontend/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Styled from 'styled-components';

import Logo from './../../assets/images/logo_white.png';
import SidebarMenuNavigationButton from '../buttons/sidebar-menu-navigation-button';
import NetworkSelectionDropdown from '../dropdowns/network-selection-dropdown';
import { useSidebarContext } from '../../contexts';

export default (): JSX.Element => {
const { isOpen } = useSidebarContext();
return (
<Sidebar.Layout isOpen={isOpen}>
<Sidebar.Top>
<Sidebar.Logo src={Logo} />
</Sidebar.Top>
<Sidebar.Menu>
<NetworkSelectionDropdown />
<SidebarMenuNavigationButton text="Wallet" href="/wallet" exact={true} />
<SidebarMenuNavigationButton text="Chat" href="/chat" exact={true} />
<SidebarMenuNavigationButton text="Models" href="/models" exact={true} />
<SidebarMenuNavigationButton text="Agents" href="/agents" exact={true} />
<Sidebar.SeparatorLine />
<SidebarMenuNavigationButton text="Provider Hub" href="/provider" exact={true} />
<SidebarMenuNavigationButton text="Sessions" href="/session" exact={true} />
<Sidebar.SeparatorLine />
</Sidebar.Menu>
<Sidebar.Bottom>MorpheusAI.org</Sidebar.Bottom>
</Sidebar.Layout>
);
};

type SidebarLayoutProps = {
isOpen: boolean;
};

const Sidebar = {
Layout: Styled.div<SidebarLayoutProps>`
width: ${(props) => (props.isOpen ? '250px' : '0px')};
height: 100%;
background: black;
display: flex;
flex-direction: column;
flex-shrink: 0;
transition: 0.2s;
`,
Top: Styled.div`
height: 50px;
display: flex;
justify-content: center;
flex-shrink: 0;
`,
Logo: Styled.img`
height: 50px;
width: 50px;
`,
Menu: Styled.div`
flex-grow: 1;
overflow-y: auto;
`,
MenuItem: Styled.div`
display: flex;
align-items: center;
`,

Bottom: Styled.div`
height: 50px;
flex-shrink: 0;
color: ${(props) => props.theme.colors.balance};
font-family: ${(props) => props.theme.fonts.family.primary.regular};
font-size: ${(props) => props.theme.fonts.size.small};
display: flex;
align-items: center;
justify-content: center;
`,
SeparatorLine: Styled.div`
height: 1px;
background: ${(props) => props.theme.colors.gray};
`,
};
7 changes: 1 addition & 6 deletions src/frontend/components/layout/top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default () => {
<TopBar.MinimizeButton onClick={onMinimizeClicked} /> */}
</TopBar.Left>
<TopBar.Middle>
<TopBar.Logo src={logo} />
{/* <TopBar.Logo src={logo} />*/}
<TopBar.Header>Morpheus</TopBar.Header>
</TopBar.Middle>
<TopBar.Right>
Expand Down Expand Up @@ -150,11 +150,6 @@ const TopBar = {
height: 25px;
cursor: pointer;
`,
Logo: Styled.img`
display: flex;
height: 100px;
width: 100px;
`,
Header: Styled.h2`
font-size: ${(props) => props.theme.fonts.size.medium};
font-family: ${(props) => props.theme.fonts.family.primary.regular};
Expand Down
44 changes: 43 additions & 1 deletion src/frontend/contexts.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { createContext, PropsWithChildren, useContext, useState } from 'react';
import {
createContext,
PropsWithChildren,
useContext,
useState,
SetStateAction,
Dispatch,
} from 'react';
import { AIMessage } from './types';

export type AIMessagesContextType = [Array<AIMessage>, (messages: Array<AIMessage>) => void];
Expand Down Expand Up @@ -29,3 +36,38 @@ export const useAIMessagesContext = () => {

return context;
};

export type SidebarContextType = {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
};

export const SidebarContext = createContext<SidebarContextType>({
isOpen: false,
setIsOpen: (isOpen: boolean) => {},
});

export const SidebarProvider = ({ children }: PropsWithChildren) => {
const [isOpen, setIsOpen] = useState<boolean>(false);

return (
<SidebarContext.Provider
value={{
isOpen,
setIsOpen,
}}
>
{children}
</SidebarContext.Provider>
);
};

export const useSidebarContext = () => {
const context = useContext(SidebarContext);

if (!context) {
throw new Error(`useSidebarContext must be used within SidebarProvider`);
}

return context;
};
Loading

0 comments on commit c29eb65

Please sign in to comment.