Skip to content

Commit

Permalink
Merge pull request #181 from penumbra-zone/navbar-mobile-ui-rework
Browse files Browse the repository at this point in the history
Navbar mobile UI rework
  • Loading branch information
ejmg authored Aug 27, 2024
2 parents 9399ebc + 7844ac4 commit c58e429
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 46 deletions.
3 changes: 1 addition & 2 deletions apps/web/src/app/(index)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ interface CardProps {
}

const LandingCard: FC<CardProps> = ({ heading, children, className, buttonText, buttonLink, disabled = false }) => {
console.log(heading, disabled);
return (
<Card className={cn("bg-card/60", className)}>
<CardHeader>
Expand Down Expand Up @@ -75,7 +74,7 @@ export default function Home() {
// console.log(queryClient)

return (
<div className="flex flex-wrap gap-3 items-center justify-between py-5">
<div className="flex flex-wrap gap-3 items-center justify-between">
<LandingCard
heading="Transactions"
buttonLink="/transactions"
Expand Down
64 changes: 37 additions & 27 deletions apps/web/src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ import {
// } from "@/components/ui/dropdown-menu";
import { usePathname } from "next/navigation";

const RadiantLogoDark = ({ width, height, className } : { width: number, height: number, className?: string }) => {
const RadiantLogoDark = ({ className } : { className?: string }) => {
return (
<div className={className}>
<Image src={radiantLogoDark} alt="RadiantCommons.com Logo" width={width} height={height} priority/>
<Image src={radiantLogoDark} alt="RadiantCommons.com Logo" priority/>
</div>
);
};

const RadiantLogoLight = ({ width, height, className } : { width: number, height: number, className?: string }) => {
const RadiantLogoLight = ({ className } : { className?: string }) => {
return (
<div className={className}>
<Image src={radiantLogoLight} alt="RadiantCommons.com Logo" width={width} height={height} priority/>
<Image src={radiantLogoLight} alt="RadiantCommons.com Logo" priority/>
</div>
);
};
Expand All @@ -51,9 +51,7 @@ const RadiantLogoLight = ({ width, height, className } : { width: number, height
// TBQF I don't think this sort of error checking is necessary. If anything, it'll cause errors whenever new paths are updated.
// if (!segments.every(isBreadcrumbPath)) return null;

const Breadcrumbs = () => {
const pathName = usePathname();

const Breadcrumbs : FC<{ pathName: string }>= ({ pathName }) => {
// Don't show breadcrumbs if on index.
if (pathName === "/") return null;

Expand Down Expand Up @@ -129,31 +127,43 @@ const Breadcrumbs = () => {
}
};

export const Navbar : FC = () => {
export const Navbar: FC = () => {
const pathName = usePathname();
return (
<div className="flex flex-wrap justify-between items-center p-8 gap-2 max-w-[1400px] mx-auto">
<div className="flex-grow flex flex-wrap">
<Link href="https://radiantcommons.com" className="" >
<RadiantLogoDark height={48} width={48} className="dark:block hidden"/>
<RadiantLogoLight height={48} width={48} className="dark:hidden"/>
</Link>
<div className="flex items-center">
<h1 className={`font-semibold text-2xl ml-1 mr-3 ${workSans.className}`}><Link href="/">Cuiloa</Link></h1>
<div className="flex flex-wrap justify-between items-center px-4 py-8 sm:px-8 sm:py-16 sm:gap-2 gap-0 max-w-[1400px] mx-auto">
<div className="flex flex-wrap grow items-center sm:w-auto w-2/3">
<Link href="https://radiantcommons.com">
<RadiantLogoDark className="sm:w-12 sm:h-12 w-9 h-9 dark:block hidden" />
<RadiantLogoLight className="sm:w-12 sm:h-12 w-9 h-9 dark:hidden" />
</Link>
<h1
className={`font-semibold text-2xl ml-1 mr-3 ${workSans.className}`}
>
<Link href="/" className="hover:underline">
Cuiloa
</Link>
</h1>
{/* NOTE: the 5px of padding-top is to better align the smaller text with the text above, please keep it. */}
<p className={`text-link font-medium pt-[5px] ${workSans.className}`}>
<Link href="https://penumbra.zone/" className="">
A Block Explorer For Penumbra
<p
className={`sm:w-fit w-2/3 sm:basis-auto basis-full font-medium pt-[5px] ${workSans.className}`}
>
<Link
href="https://penumbra.zone/"
className="hover:underline text-link"
>
Block Explorer For Penumbra
</Link>
</p>
</div>
<div className="flex items-center gap-2 sm:w-auto mb-auto">
<SearchBar className="w-9 h-9 sm:w-40 md:w-56 lg:w-80 sm:h-11" />
<ThemeToggleButton className="w-9 sm:w-11 h-9 sm:h-11" />
</div>
{pathName !== "/" ? (
<div className="w-full h-5 pt-2 sm:pt-0">
<Breadcrumbs pathName={pathName} />
</div>
) : null}
</div>
<div className="flex items-center gap-2">
<SearchBar className="w-5/6 sm:max-w-40"/>
<ThemeToggleButton />
</div>
<div className="w-full h-5">
<Breadcrumbs />
</div>
</div>
);
};
83 changes: 70 additions & 13 deletions apps/web/src/components/Searchbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"use client";

import { type FC, useRef, useState, useEffect } from "react";
import { Command, CommandInput } from "../ui/command";
import { type FC, useRef, useState, useEffect, useCallback } from "react";
import { CommandInput, CommandDialog } from "../ui/command";
import { useToast } from "@/components/ui/use-toast";
import { usePathname, useRouter } from "next/navigation";
import { useOnClickOutside } from "usehooks-ts";
import { SearchValidator } from "@/lib/validators/search";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button";
import { Search } from "lucide-react";
import { Input } from "../ui/input";

interface SearchProps {
className?: string;
Expand All @@ -18,6 +21,8 @@ const SearchBar : FC<SearchProps> = ({ className }) => {
const [input, setInput] = useState<string>("");
const cmdRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);

const [open, setOpen] = useState(false);
const { toast } = useToast();

useOnClickOutside(cmdRef, () => {
Expand All @@ -30,19 +35,70 @@ const SearchBar : FC<SearchProps> = ({ className }) => {
cmdRef.current?.blur();
}, [pathname]);

const searchCmd = () => {
router.push(`/search/${input}`);
};
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);

const search = useCallback((command: () => unknown) => {
setOpen(false);
command();
}, []);

return (
<Command
ref={cmdRef}
className={cn("relative rounded-full bg-popover border max-w-lg z-50 overflow-visible", className)}
shouldFilter={false}>
<div className={cn("", className)}>
<div className="hidden sm:inline-flex relative items-center rounded-full bg-popover border w-full z-50 overflow-visible pl-3 gap-2">
<Search className="w-5 h-5"/>
<Input
ref={inputRef}
className={"border-none px-0 mt-[2px] rounded-full focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:ring-transparent"}
placeholder="Search"
value={input}
onChange={(text) => {
console.log("text: ", text.currentTarget.value);
setInput(text.currentTarget.value);
}}
onKeyDown={(e) => {
// Aside: Now that this is just a single command input, maybe just convert this to a generic input box?
if (e.key === "Enter" && input.length !== 0) {
const searchQuery = SearchValidator.safeParse(input);
if (searchQuery.success) {
search(() => router.push(`/search/${searchQuery.data.value as string}`));
}
else {
toast({
variant: "destructive",
title: "Invalid search query.",
description: "Try again with a block height, hash hash, or IBC identifier.",
});
}
}
}}
/>
<kbd className="pointer-events-none hidden h-5 w-5 mr-3 select-none items-center bg-popover font-mono font-medium opacity-100 sm:flex text-xs text-muted-foreground/75">
⌘K
</kbd>
</div>
<Button
variant="outline"
size="icon"
className={cn(
"sm:hidden rounded-full border h-9 w-9 justify-center items-center bg-popover text-sm font-normal shadow-none",
)}
onClick={() => setOpen(true)}
>
<Search className="w-4 h-4"/>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput
className="text-sm"
ref={inputRef}
placeholder="Search..."
placeholder="Search for transactions, blocks, IBC data..."
value={input}
onValueChange={(text) => {
setInput(text);
Expand All @@ -52,7 +108,7 @@ const SearchBar : FC<SearchProps> = ({ className }) => {
if (e.key === "Enter" && input.length !== 0) {
const searchQuery = SearchValidator.safeParse(input);
if (searchQuery.success) {
searchCmd();
search(() => router.push(`/search/${searchQuery.data.value as string}`));
}
else {
toast({
Expand All @@ -64,7 +120,8 @@ const SearchBar : FC<SearchProps> = ({ className }) => {
}
}}
/>
</Command>
</CommandDialog>
</div>
);
};

Expand Down
14 changes: 10 additions & 4 deletions apps/web/src/components/ThemeToggleButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"use client";

import { useState, useEffect } from "react";
import { useState, useEffect, FC } from "react";
import { LeftPartialEclipse, RightPartialEclipse } from "./EclipseIcon";
import { useTheme } from "next-themes";
import { Button } from "../ui/button";
import { cn } from "@/lib/utils";

export const ThemeToggleButton = () => {

interface ThemeToggleProps {
className?: string;
}

export const ThemeToggleButton : FC<ThemeToggleProps> = ({ className }) => {
const { setTheme, theme } = useTheme();
const [mounted, setMounted] = useState(false);

Expand All @@ -18,8 +24,8 @@ export const ThemeToggleButton = () => {
const isLight = theme === "light";

return (
<Button className="rounded-full" variant="outline" size="icon" onClick={() => setTheme(isLight ? "dark" : "light")}>
{isLight ? <LeftPartialEclipse height={16} width={16}/>: <RightPartialEclipse height={16} width={16} />}
<Button className={cn("rounded-full", className)} variant="outline" size="icon" onClick={() => setTheme(isLight ? "dark" : "light")}>
{isLight ? <LeftPartialEclipse height={16} width={16}/> : <RightPartialEclipse height={16} width={16} />}
</Button>
);
};
25 changes: 25 additions & 0 deletions apps/web/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react";

import { cn } from "@/lib/utils";

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";

export { Input };

0 comments on commit c58e429

Please sign in to comment.