diff --git a/wallet/src/ui/app/components/menu/button/MenuButton.module.scss b/wallet/src/ui/app/components/menu/button/MenuButton.module.scss
new file mode 100644
index 0000000000000..9a3ab8a0536bc
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/button/MenuButton.module.scss
@@ -0,0 +1,55 @@
+@use '_variables' as v;
+
+$btn-width: 20px;
+$btn-height: 14px;
+$line-height: 2px;
+
+.button {
+ background: none;
+ outline: none;
+ border: none;
+ width: $btn-width;
+ height: $btn-height;
+ position: relative;
+ cursor: pointer;
+ padding: 0;
+}
+
+.line {
+ width: $btn-width;
+ height: $line-height;
+ background-color: v.use(v.$colors-menu-btn-color);
+ transition-property: opacity, transform, top;
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in-out;
+ display: block;
+ position: absolute;
+ border-radius: 10px;
+
+ &.line-1 {
+ top: 0;
+ }
+
+ &.line-2 {
+ top: calc(50% - ($line-height / 2));
+ }
+
+ &.line-3 {
+ bottom: 0;
+ }
+}
+
+.open {
+ > .line-1 {
+ top: calc(50% - ($line-height / 2));
+ transform: rotate(45deg);
+ }
+
+ > .line-2 {
+ transform: rotate(-45deg);
+ }
+
+ > .line-3 {
+ opacity: 0;
+ }
+}
diff --git a/wallet/src/ui/app/components/menu/button/index.tsx b/wallet/src/ui/app/components/menu/button/index.tsx
new file mode 100644
index 0000000000000..9d01588c3b6b6
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/button/index.tsx
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import cl from 'classnames';
+import { memo } from 'react';
+import { Link } from 'react-router-dom';
+
+import { useMenuIsOpen, useNextMenuUrl } from '_components/menu/hooks';
+
+import st from './MenuButton.module.scss';
+
+export type MenuButtonProps = {
+ className?: string;
+};
+
+function MenuButton({ className }: MenuButtonProps) {
+ const isOpen = useMenuIsOpen();
+ const menuUrl = useNextMenuUrl(!isOpen, '/');
+ return (
+
+
+
+
+
+ );
+}
+
+export default memo(MenuButton);
diff --git a/wallet/src/ui/app/components/menu/content/MenuContent.module.scss b/wallet/src/ui/app/components/menu/content/MenuContent.module.scss
new file mode 100644
index 0000000000000..e107d129f4db6
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/content/MenuContent.module.scss
@@ -0,0 +1,25 @@
+@use '_values/colors';
+@use '_variables' as v;
+
+.container {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.backdrop {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: colors.$gray-100;
+ opacity: 0.8;
+}
+
+.content {
+ position: relative;
+ padding: 20px 25px 25px;
+ background-color: v.use(v.$colors-main-content-background);
+ border-radius: 20px;
+ max-height: 100%;
+ overflow-y: auto;
+}
diff --git a/wallet/src/ui/app/components/menu/content/index.tsx b/wallet/src/ui/app/components/menu/content/index.tsx
new file mode 100644
index 0000000000000..bbb2a6c692452
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/content/index.tsx
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { useCallback } from 'react';
+import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
+
+import Settings from './settings';
+import {
+ useMenuIsOpen,
+ useMenuUrl,
+ useNextMenuUrl,
+} from '_components/menu/hooks';
+import { useOnKeyboardEvent } from '_hooks';
+
+import type { MouseEvent } from 'react';
+
+import st from './MenuContent.module.scss';
+
+const CLOSE_KEY_CODES: string[] = ['Escape'];
+
+function MenuContent() {
+ const isOpen = useMenuIsOpen();
+ const menuUrl = useMenuUrl();
+ const menuHomeUrl = useNextMenuUrl(true, '/settings');
+ const closeMenuUrl = useNextMenuUrl(false);
+ const navigate = useNavigate();
+ const handleOnCloseMenu = useCallback(
+ (e: KeyboardEvent | MouseEvent) => {
+ if (isOpen) {
+ e.preventDefault();
+ navigate(closeMenuUrl);
+ }
+ },
+ [isOpen, navigate, closeMenuUrl]
+ );
+ useOnKeyboardEvent('keydown', CLOSE_KEY_CODES, handleOnCloseMenu, isOpen);
+ if (!isOpen) {
+ return null;
+ }
+ return (
+
+
+
+
+ } />
+ }
+ />
+
+
+
+ );
+}
+
+export default MenuContent;
diff --git a/wallet/src/ui/app/pages/home/settings/SettingsPage.module.scss b/wallet/src/ui/app/components/menu/content/settings/SettingsPage.module.scss
similarity index 100%
rename from wallet/src/ui/app/pages/home/settings/SettingsPage.module.scss
rename to wallet/src/ui/app/components/menu/content/settings/SettingsPage.module.scss
diff --git a/wallet/src/ui/app/pages/home/settings/index.tsx b/wallet/src/ui/app/components/menu/content/settings/index.tsx
similarity index 98%
rename from wallet/src/ui/app/pages/home/settings/index.tsx
rename to wallet/src/ui/app/components/menu/content/settings/index.tsx
index 29858a91a8156..69d01b8d08f6e 100644
--- a/wallet/src/ui/app/pages/home/settings/index.tsx
+++ b/wallet/src/ui/app/components/menu/content/settings/index.tsx
@@ -20,7 +20,7 @@ import type { SerializedError } from '@reduxjs/toolkit';
import st from './SettingsPage.module.scss';
-function SettingsPage() {
+function Settings() {
const [logoutInProgress, setLogoutInProgress] = useState(false);
const [mintInProgress, setMintInProgress] = useState(false);
const [mintStatus, setMintStatus] = useState(null);
@@ -114,4 +114,4 @@ function SettingsPage() {
);
}
-export default SettingsPage;
+export default Settings;
diff --git a/wallet/src/ui/app/components/menu/hooks.ts b/wallet/src/ui/app/components/menu/hooks.ts
new file mode 100644
index 0000000000000..1360d932fd746
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/hooks.ts
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { useMemo } from 'react';
+import { useLocation, useSearchParams } from 'react-router-dom';
+
+const MENU_PARAM = 'menu';
+
+export function useMenuUrl() {
+ const [searchParams] = useSearchParams();
+ if (searchParams.has(MENU_PARAM)) {
+ return searchParams.get(MENU_PARAM) || '/';
+ }
+ return false;
+}
+
+export function useMenuIsOpen() {
+ const [searchParams] = useSearchParams();
+ return searchParams.has(MENU_PARAM);
+}
+
+/**
+ * Get the URL that contains the background page and the menu location
+ *
+ * @param isOpen Indicates if the menu will be open
+ * @param nextMenuLocation The location within the menu
+ */
+export function useNextMenuUrl(isOpen: boolean, nextMenuLocation = '/') {
+ const [searchParams] = useSearchParams();
+ const { pathname } = useLocation();
+ return useMemo(() => {
+ if (isOpen) {
+ searchParams.set(MENU_PARAM, nextMenuLocation);
+ } else {
+ searchParams.delete(MENU_PARAM);
+ }
+ const search = searchParams.toString();
+ return `${pathname}${search ? '?' : ''}${search}`;
+ }, [isOpen, nextMenuLocation, searchParams, pathname]);
+}
diff --git a/wallet/src/ui/app/components/menu/index.tsx b/wallet/src/ui/app/components/menu/index.tsx
new file mode 100644
index 0000000000000..93c40e17c2981
--- /dev/null
+++ b/wallet/src/ui/app/components/menu/index.tsx
@@ -0,0 +1,5 @@
+// Copyright (c) 2022, Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+export { default as MenuButton } from './button';
+export { default as MenuContent } from './content';
diff --git a/wallet/src/ui/app/components/navigation/index.tsx b/wallet/src/ui/app/components/navigation/index.tsx
index 5b20f0e392d21..33c240aa88563 100644
--- a/wallet/src/ui/app/components/navigation/index.tsx
+++ b/wallet/src/ui/app/components/navigation/index.tsx
@@ -36,10 +36,6 @@ function Navigation({ className }: NavigationProps) {
Activity
-
-
- Settings
-
);
}
diff --git a/wallet/src/ui/app/hooks/index.ts b/wallet/src/ui/app/hooks/index.ts
index 8aacd191d0c94..8552ebae9b6a5 100644
--- a/wallet/src/ui/app/hooks/index.ts
+++ b/wallet/src/ui/app/hooks/index.ts
@@ -10,3 +10,4 @@ export { default as useMediaUrl } from './useMediaUrl';
export { default as useSuiObjectFields } from './useSuiObjectFields';
export { default as useNumberDelimiters } from './useNumberDelimiters';
export { default as useOnClickOutside } from './useOnClickOutside';
+export { default as useOnKeyboardEvent } from './useOnKeyboardEvent';
diff --git a/wallet/src/ui/app/hooks/useOnKeyboardEvent.ts b/wallet/src/ui/app/hooks/useOnKeyboardEvent.ts
new file mode 100644
index 0000000000000..606aa21e9cbf0
--- /dev/null
+++ b/wallet/src/ui/app/hooks/useOnKeyboardEvent.ts
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { useEffect } from 'react';
+
+function useOnKeyboardEvent(
+ eventType: K,
+ keys: string[],
+ handler: (e: KeyboardEvent) => void,
+ enabled = true
+) {
+ useEffect(() => {
+ if (enabled) {
+ const listener = (e: KeyboardEvent) => {
+ if (keys.includes(e.key)) {
+ handler(e);
+ }
+ };
+
+ document.addEventListener(eventType, listener);
+
+ return () => {
+ document.removeEventListener(eventType, listener);
+ };
+ }
+ }, [eventType, keys, handler, enabled]);
+}
+
+export default useOnKeyboardEvent;
diff --git a/wallet/src/ui/app/index.tsx b/wallet/src/ui/app/index.tsx
index b18e9f4d3125f..4059b776047cd 100644
--- a/wallet/src/ui/app/index.tsx
+++ b/wallet/src/ui/app/index.tsx
@@ -9,7 +9,6 @@ import { useAppDispatch, useAppSelector } from '_hooks';
import { DappTxApprovalPage } from '_pages/dapp-tx-approval';
import HomePage, {
NftsPage,
- SettingsPage,
StakePage,
TokensPage,
TransactionDetailsPage,
@@ -49,7 +48,6 @@ const App = () => {
} />
} />
} />
- } />
} />
} />
} />
diff --git a/wallet/src/ui/app/pages/home/Home.module.scss b/wallet/src/ui/app/pages/home/Home.module.scss
index 9b6bade7971c8..604ed100c3e5e 100644
--- a/wallet/src/ui/app/pages/home/Home.module.scss
+++ b/wallet/src/ui/app/pages/home/Home.module.scss
@@ -13,10 +13,14 @@
flex-flow: row nowrap;
align-items: center;
padding: 0 15px;
- justify-content: center;
+ justify-content: space-between;
min-height: 52px;
}
+.menu-button {
+ justify-self: flex-end;
+}
+
.content {
display: flex;
flex-flow: column nowrap;
diff --git a/wallet/src/ui/app/pages/home/index.tsx b/wallet/src/ui/app/pages/home/index.tsx
index 98c4df207aae0..aea0db0156711 100644
--- a/wallet/src/ui/app/pages/home/index.tsx
+++ b/wallet/src/ui/app/pages/home/index.tsx
@@ -7,6 +7,7 @@ import { of, filter, switchMap, from, defer, repeat } from 'rxjs';
import Loading from '_components/loading';
import Logo from '_components/logo';
+import { MenuButton, MenuContent } from '_components/menu';
import Navigation from '_components/navigation';
import { useInitializedGuard, useAppDispatch } from '_hooks';
import PageLayout from '_pages/layout';
@@ -38,13 +39,16 @@ const HomePage = () => {
@@ -54,7 +58,6 @@ const HomePage = () => {
export default HomePage;
export { default as NftsPage } from './nfts';
-export { default as SettingsPage } from './settings';
export { default as StakePage } from './stake';
export { default as TokensPage } from './tokens';
export { default as TransactionDetailsPage } from './transaction-details';
diff --git a/wallet/src/ui/styles/themes/dark.scss b/wallet/src/ui/styles/themes/dark.scss
index 7fe7d6016e16d..4492a6903cb89 100644
--- a/wallet/src/ui/styles/themes/dark.scss
+++ b/wallet/src/ui/styles/themes/dark.scss
@@ -15,4 +15,5 @@ $values: (
c-var.$nav-item-highlighted-color: c-val.$white,
c-var.$nav-item-icon-highlighted-color: c-val.$sui-blue,
c-var.$nav-item-icon-highlighted-gradient-color: none,
+ c-var.$menu-btn-color: c-val.$white,
);
diff --git a/wallet/src/ui/styles/themes/light.scss b/wallet/src/ui/styles/themes/light.scss
index caca5bd7599b3..cacb897ab18e1 100644
--- a/wallet/src/ui/styles/themes/light.scss
+++ b/wallet/src/ui/styles/themes/light.scss
@@ -20,4 +20,5 @@ $values: (
c-var.$nav-item-icon-highlighted-color: transparent,
c-var.$nav-item-icon-highlighted-gradient-color:
linear-gradient(135deg, #589aea 0%, #4c75a6 100%),
+ c-var.$menu-btn-color: c-val.$gray-70,
);
diff --git a/wallet/src/ui/styles/variables/colors.scss b/wallet/src/ui/styles/variables/colors.scss
index b3d1709d82974..f16f66f07b448 100644
--- a/wallet/src/ui/styles/variables/colors.scss
+++ b/wallet/src/ui/styles/variables/colors.scss
@@ -9,3 +9,4 @@ $nav-item-color: '--colors-nav-item-color';
$nav-item-highlighted-color: '--colors-nav-item-highlighted-color';
$nav-item-icon-highlighted-color: '--colors-nav-item-icon-highlighted-color';
$nav-item-icon-highlighted-gradient-color: '--colors-nav-item-icon-highlighted-gradient-color';
+$menu-btn-color: '--colors-menu-btn-color';