From abe51e4e3f9d8db9f03905a70728700ddc45dad7 Mon Sep 17 00:00:00 2001 From: AssetMantle Dev <52166434+deepanshutr@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:04:30 +0530 Subject: [PATCH 1/2] feat(responsive): unblock mobile, add bottom tab bar, stack Transact - remove the d-none d-md-block gate + 'open on a larger device' screen so the app renders at all viewports - add a bottom tab bar (5 primary + More sheet) for < xl; keep the desktop top nav for xl+; Connect stays visible in the mobile header - relax the fixed-viewport-height shell below lg so pages flow and scroll naturally instead of each section scrolling independently - Transact: stack Send/Receive (col-12 col-lg-8 / col-12 col-lg-4) Verified: desktop 1440 unchanged; mobile 390 unblocked + stacks. --- components/Layout.js | 74 +++---------- components/MobileTabBar.jsx | 128 +++++++++++++++++++++++ components/ScrollableSectionContainer.js | 3 +- config/styles/index.scss | 76 ++++++++++++++ pages/index.jsx | 6 +- views/Header.js | 44 ++------ 6 files changed, 229 insertions(+), 102 deletions(-) create mode 100644 components/MobileTabBar.jsx diff --git a/components/Layout.js b/components/Layout.js index 8915c0ff..cda24a3f 100644 --- a/components/Layout.js +++ b/components/Layout.js @@ -1,70 +1,22 @@ -import React, { useState } from "react"; +import React from "react"; import Balance from "../views/Balance"; import Header from "../views/Header"; -// import Banner from "./Banner"; -// import Vesting from "../views/Vesting"; +import MobileTabBar from "./MobileTabBar"; import ScrollableSectionContainer from "./ScrollableSectionContainer"; export default function Layout({ children }) { - const [leftCol, setLeftCol] = useState(false); return ( - <> -
- {/* */} -
-
-
- - - {/* */} - {leftCol && ( -
setLeftCol(false)} - > - -
- )} -
-
{children}
-
-
-
-
-
-
-
- - - -
- -
-
- -

- Please open the app on a Tab or Larger device. -

- -
+
+
+
+
+ + + +
{children}
-
- + + +
); } diff --git a/components/MobileTabBar.jsx b/components/MobileTabBar.jsx new file mode 100644 index 00000000..0ca2a65d --- /dev/null +++ b/components/MobileTabBar.jsx @@ -0,0 +1,128 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; +import React, { useState } from "react"; +import { NavBarData } from "../data"; + +// Bottom tab bar for < xl viewports (phone + tablet). The desktop top nav in +// Header.js stays for xl+. Five primary destinations sit on the bar; the rest +// of NavBarData.navs (plus the external links) live in a "More" sheet so every +// section stays reachable on a phone. +const PRIMARY_HREFS = ["/", "/stake", "/vote", "/bridge"]; + +function isActive(router, href) { + return router.asPath === href || router.asPath.split("?")[0] === href; +} + +function TabLink({ item, active }) { + return ( + + + {item.icon} + {item.title || "Help"} + + + ); +} + +export default function MobileTabBar() { + const router = useRouter(); + const [moreOpen, setMoreOpen] = useState(false); + + const primary = PRIMARY_HREFS.map((href) => + NavBarData.navs.find((n) => n.href === href) + ).filter(Boolean); + const overflow = [ + ...NavBarData.navs.filter((n) => !PRIMARY_HREFS.includes(n.href)), + ...NavBarData.rightNav, + ]; + const moreActive = overflow.some( + (n) => !n.target && isActive(router, n.href) + ); + + return ( + <> + {moreOpen && ( +
setMoreOpen(false)} + /> + )} +
+
+

More

+ +
+
+ {overflow.map((item, i) => ( + + setMoreOpen(false)} + className={`d-flex align-items-center gap-3 px-4 py-3 text-white am-tab-row ${ + !item.target && isActive(router, item.href) + ? "active text-primary" + : "" + }`} + > + {item.icon} + {item.title || "Help"} + {item.endIcon && ( + {item.endIcon} + )} + + + ))} +
+
+ + + + ); +} diff --git a/components/ScrollableSectionContainer.js b/components/ScrollableSectionContainer.js index e814b0e0..2077c4da 100644 --- a/components/ScrollableSectionContainer.js +++ b/components/ScrollableSectionContainer.js @@ -3,8 +3,7 @@ import React from "react"; export default function ScrollableSectionContainer({ className, children }) { return (
{children}
diff --git a/config/styles/index.scss b/config/styles/index.scss index 2f91b1d1..76ba9322 100644 --- a/config/styles/index.scss +++ b/config/styles/index.scss @@ -1310,3 +1310,79 @@ body::before { opacity: 1; } } + +// Mobile bottom tab bar + "More" sheet (< xl). Surface + active accent reuse +// .nav-bg / .text-primary from the theme; only layout/animation live here. +.am-tab-bar { + border-top: 1px solid $white-400; + padding-bottom: env(safe-area-inset-bottom, 0px); + min-height: 56px; +} +.am-tab { + padding: 0.5rem 0.25rem; + text-decoration: none; + background: transparent; + border: 0; + transition: color 150ms ease; + .caption2 { + line-height: 1; + } + &:focus-visible { + outline: 2px solid $primary-main; + outline-offset: -2px; + } +} +.am-tab-scrim { + background: rgba(0, 0, 0, 0.5); +} +.am-tab-sheet { + bottom: calc(56px + env(safe-area-inset-bottom, 0px)); + border-top: 1px solid $white-400; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + max-height: 70vh; + overflow-y: auto; + transform: translateY(150%); + transition: transform 220ms ease; + &.open { + transform: translateY(0); + } +} +.am-tab-row { + text-decoration: none; + &:active { + background: rgba(255, 255, 255, 0.06); + } +} +// keep page content clear of the fixed bar on < xl +@media (max-width: 1199.98px) { + .am_app_container { + padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); + } +} +@media (prefers-reduced-motion: reduce) { + .am-tab-sheet { + transition: none; + } +} + +// Desktop uses a fixed-viewport-height shell where each section scrolls on its +// own (.am-scroll-section). On phone/tablet (< lg) that fights natural reading: +// relax the height chain so the PAGE flows and scrolls, and sections size to +// their content and stack. +.am-scroll-section { + overflow-y: auto; +} +@media (max-width: 991.98px) { + .am_app_container .am-scroll-section { + height: auto !important; + overflow-y: visible; + padding-bottom: 1rem; + } + .am_app_container main.h-100, + .am_app_container .row.h-100, + .am_app_container .col-lg-9.h-100, + .am_app_container .col-lg-3 { + height: auto !important; + } +} diff --git a/pages/index.jsx b/pages/index.jsx index 17a788e0..8c7b11f0 100644 --- a/pages/index.jsx +++ b/pages/index.jsx @@ -404,8 +404,8 @@ export default function Transact() { Transact | MantleWallet -
- +
+
- + {status === "Connected" ? ( ) : ( diff --git a/views/Header.js b/views/Header.js index cbebf36e..925b3fcd 100644 --- a/views/Header.js +++ b/views/Header.js @@ -15,14 +15,11 @@ import { } from "../data"; import { cleanString, shortenAddress } from "../lib"; -export default function Header({ setLeftCol }) { +export default function Header() { const chainContext = useChain(defaultChainName); const { username, address, wallet, disconnect } = chainContext; const [showModal, setShowModal] = useState(false); - // menus ham - const [rightHam, setRightHam] = useState(false); - const router = useRouter(); // Events @@ -187,46 +184,21 @@ export default function Header({ setLeftCol }) { className="container-xxl d-flex align-items-center gap-3 p-3 px-4" style={{ maxWidth: "1920px" }} > -
setLeftCol(true)} - > - -
{appLogoJSX} -