diff --git a/apps/web/src/app/(index)/page.tsx b/apps/web/src/app/(index)/page.tsx index 931b451..4c1f39c 100644 --- a/apps/web/src/app/(index)/page.tsx +++ b/apps/web/src/app/(index)/page.tsx @@ -28,7 +28,6 @@ interface CardProps { } const LandingCard: FC = ({ heading, children, className, buttonText, buttonLink, disabled = false }) => { - console.log(heading, disabled); return ( @@ -75,7 +74,7 @@ export default function Home() { // console.log(queryClient) return ( -
+
{ +const RadiantLogoDark = ({ className } : { className?: string }) => { return (
- RadiantCommons.com Logo + RadiantCommons.com Logo
); }; -const RadiantLogoLight = ({ width, height, className } : { width: number, height: number, className?: string }) => { +const RadiantLogoLight = ({ className } : { className?: string }) => { return (
- RadiantCommons.com Logo + RadiantCommons.com Logo
); }; @@ -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; @@ -129,31 +127,43 @@ const Breadcrumbs = () => { } }; -export const Navbar : FC = () => { +export const Navbar: FC = () => { + const pathName = usePathname(); return ( -
-
- - - - -
-

Cuiloa

+
+
+ + + + +

+ + Cuiloa + +

{/* NOTE: the 5px of padding-top is to better align the smaller text with the text above, please keep it. */} -

- - A Block Explorer For Penumbra +

+ + Block Explorer For Penumbra

+
+ + +
+ {pathName !== "/" ? ( +
+ +
+ ) : null}
-
- - -
-
- -
-
); }; diff --git a/apps/web/src/components/Searchbar/index.tsx b/apps/web/src/components/Searchbar/index.tsx index 9846df4..61288a4 100644 --- a/apps/web/src/components/Searchbar/index.tsx +++ b/apps/web/src/components/Searchbar/index.tsx @@ -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; @@ -18,6 +21,8 @@ const SearchBar : FC = ({ className }) => { const [input, setInput] = useState(""); const cmdRef = useRef(null); const inputRef = useRef(null); + + const [open, setOpen] = useState(false); const { toast } = useToast(); useOnClickOutside(cmdRef, () => { @@ -30,19 +35,70 @@ const SearchBar : FC = ({ 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 ( - +
+
+ + { + 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.", + }); + } + } + }} + /> + + ⌘K + +
+ + { setInput(text); @@ -52,7 +108,7 @@ const SearchBar : FC = ({ 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({ @@ -64,7 +120,8 @@ const SearchBar : FC = ({ className }) => { } }} /> - + +
); }; diff --git a/apps/web/src/components/ThemeToggleButton/index.tsx b/apps/web/src/components/ThemeToggleButton/index.tsx index 7602696..c6de450 100644 --- a/apps/web/src/components/ThemeToggleButton/index.tsx +++ b/apps/web/src/components/ThemeToggleButton/index.tsx @@ -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 = ({ className }) => { const { setTheme, theme } = useTheme(); const [mounted, setMounted] = useState(false); @@ -18,8 +24,8 @@ export const ThemeToggleButton = () => { const isLight = theme === "light"; return ( - ); }; diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx new file mode 100644 index 0000000..9d631e7 --- /dev/null +++ b/apps/web/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input };