From 566d59b8d3504760e78ed213ef045ce9a83beaf4 Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Thu, 14 May 2026 15:55:21 +0200 Subject: [PATCH] initial shell redesign --- src/App.tsx | 37 +++-- src/pages/AppShell.tsx | 272 ++++++++++++++++++++++++++++++++++ src/pages/Header.tsx | 146 ------------------ src/pages/Network/Network.tsx | 3 + 4 files changed, 292 insertions(+), 166 deletions(-) create mode 100644 src/pages/AppShell.tsx delete mode 100644 src/pages/Header.tsx diff --git a/src/App.tsx b/src/App.tsx index bcee967..56ed4bd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,35 @@ import { VaultTxModal } from "polkahub" import { Navigate, Route, Routes } from "react-router-dom" +import { ConnectionState } from "./components/ConnectionState" import { Accounts } from "./pages/Accounts/Accounts" +import { AppShell } from "./pages/AppShell" import { Constants } from "./pages/Constants" import { Explorer } from "./pages/Explorer" import { Extrinsics } from "./pages/Extrinsics" -import { Header } from "./pages/Header" import { Metadata } from "./pages/Metadata" import { RpcCalls } from "./pages/RpcCalls" import { RuntimeCalls } from "./pages/RuntimeCalls" import { Storage } from "./pages/Storage" import { Transactions } from "./pages/Transactions" import { ViewFns } from "./pages/ViewFns" -import { ConnectionState } from "./components/ConnectionState" export default function App() { return ( -
-
-
-
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
-
+
+ + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + diff --git a/src/pages/AppShell.tsx b/src/pages/AppShell.tsx new file mode 100644 index 0000000..c5c3f97 --- /dev/null +++ b/src/pages/AppShell.tsx @@ -0,0 +1,272 @@ +import SliderToggle from "@/components/Toggle" +import { Button } from "@/components/ui/button" +import { Sheet, SheetContent } from "@/components/ui/sheet" +import { Link, useNavigate } from "@/hashParams" +import { changeTheme, useTheme } from "@/ThemeProvider" +import { + Blocks, + Code2, + Database, + FileCode2, + FileText, + Menu, + RadioTower, + Search, + Send, + Wallet, +} from "lucide-react" +import { FC, FormEvent, PropsWithChildren, useState } from "react" +import { useLocation } from "react-router-dom" +import { twMerge } from "tailwind-merge" +import { NetworkSwitcher } from "./Network/Network" + +type NavigationItem = { + path: string + label: string + icon: FC<{ size?: number; className?: string }> +} + +const navigationGroups: Array<{ + label: string + items: NavigationItem[] +}> = [ + { + label: "Core", + items: [ + { path: "/explorer", label: "Explorer", icon: Blocks }, + { path: "/storage", label: "Storage", icon: Database }, + { path: "/extrinsics", label: "Extrinsics", icon: Send }, + { path: "/constants", label: "Constants", icon: FileText }, + { path: "/runtimeCalls", label: "Runtime Calls", icon: Code2 }, + { path: "/accounts", label: "Accounts", icon: Wallet }, + ], + }, + { + label: "Tools", + items: [ + { path: "/viewFns", label: "View Functions", icon: FileCode2 }, + { path: "/metadata", label: "Metadata", icon: Blocks }, + { path: "/rpcCalls", label: "RPC Calls", icon: RadioTower }, + ], + }, +] + +const navigationItems = navigationGroups.flatMap((group) => group.items) + +export const AppShell: FC = ({ children }) => { + const [sidebarOpen, setSidebarOpen] = useState(false) + + return ( +
+ + + + setSidebarOpen(false)} mobile /> + + +
+ setSidebarOpen(true)} /> +
+
+ {children} +
+
+
+
+ ) +} + +const SidebarContent: FC<{ mobile?: boolean; onNavigate?: () => void }> = ({ + mobile, + onNavigate, +}) => ( +
+
+ papi logo + papi logo +
+
+ papi{" "} + console +
+
beta
+
+
+ {mobile ? ( +
+ +
+ ) : null} + + +
+) + +const TopBar: FC<{ onOpenSidebar: () => void }> = ({ onOpenSidebar }) => { + const location = useLocation() + const activeItem = getActiveNavigationItem(location.pathname) + + return ( +
+ +
+ Section + {activeItem.label} +
+ +
+ +
+
+ +
+
+ ) +} + +const GlobalJumpSearch = () => { + const navigate = useNavigate() + const [value, setValue] = useState("") + + const handleSubmit = (evt: FormEvent) => { + evt.preventDefault() + + const target = getJumpTarget(value) + if (!target) return + + navigate(target) + setValue("") + } + + return ( +
+ +
+ ) +} + +const SidebarLink: FC<{ item: NavigationItem; onClick?: () => void }> = ({ + item, + onClick, +}) => { + const location = useLocation() + const active = isNavigationItemActive(location.pathname, item.path) + const Icon = item.icon + + return ( + + + {item.label} + + ) +} + +const getActiveNavigationItem = (pathname: string) => + navigationItems.find((item) => isNavigationItemActive(pathname, item.path)) ?? + navigationItems[0] + +const isNavigationItemActive = (pathname: string, path: string) => + pathname === path || pathname.startsWith(`${path}/`) + +const getJumpTarget = (value: string) => { + const query = value.trim() + if (!query) return null + + if (query.startsWith("/")) return query + + const normalizedQuery = normalizeLabel(query) + const exactSection = navigationItems.find( + ({ label }) => normalizeLabel(label) === normalizedQuery, + ) + if (exactSection) return exactSection.path + + const matchingSection = navigationItems.find(({ label }) => + normalizeLabel(label).startsWith(normalizedQuery), + ) + if (matchingSection) return matchingSection.path + + if (/^(0x[0-9a-f]+|\d+)$/i.test(query)) { + return `/explorer/${query}` + } + + return null +} + +const normalizeLabel = (value: string) => + value.toLowerCase().replace(/[\s_-]/g, "") + +const ThemeToggle = () => { + const theme = useTheme() + + return ( + + ) +} diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx deleted file mode 100644 index f851163..0000000 --- a/src/pages/Header.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Button } from "@/components/ui/button" -import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" -import { Link } from "@/hashParams" -import { Menu } from "lucide-react" -import { FC, PropsWithChildren, useState } from "react" -import { useLocation } from "react-router-dom" -import { twMerge } from "tailwind-merge" -import { NetworkSwitcher } from "./Network/Network" -import SliderToggle from "@/components/Toggle" -import { changeTheme, useTheme } from "@/ThemeProvider" - -const navigationItems = [ - { path: "/explorer", label: "Explorer", important: true }, - { path: "/storage", label: "Storage", important: true }, - { path: "/extrinsics", label: "Extrinsics", important: true }, - { path: "/constants", label: "Constants", important: false }, - { path: "/runtimeCalls", label: "Runtime Calls", important: true }, - { path: "/accounts", label: "Accounts", important: false }, -] - -export const Header = () => { - const [open, setIsOpen] = useState(false) - - return ( -
-
-
- papi-logo - papi-logo -

- papi console -

-
- (beta) -
-
- -
- - - - - - - - - setIsOpen(false)}> - View Functions - - setIsOpen(false)}> - Metadata - - setIsOpen(false)}> - RPC Calls - -
- -
- - - -
-
- ) -} - -const NavLink: FC< - PropsWithChildren<{ to: string; onClick?: () => void; className?: string }> -> = ({ to, children, className, onClick }) => { - const location = useLocation() - const active = location.pathname.startsWith(to) - - return ( - - {children} - - ) -} - -const ThemeToggle = () => { - const theme = useTheme() - - return ( - - ) -} diff --git a/src/pages/Network/Network.tsx b/src/pages/Network/Network.tsx index 59891a1..15a6b61 100644 --- a/src/pages/Network/Network.tsx +++ b/src/pages/Network/Network.tsx @@ -37,8 +37,10 @@ import { FC, useState } from "react" import { twMerge } from "tailwind-merge" export function NetworkSwitcher({ + className, forSmallScreen, }: { + className?: string forSmallScreen?: boolean }) { const [open, setOpen] = useState(false) @@ -93,6 +95,7 @@ export function NetworkSwitcher({ className={twMerge( "w-[200px] gap-0 justify-between text-base px-3 border border-border bg-input self-center", forSmallScreen ? "flex md:hidden" : "hidden md:flex", + className, )} >