diff --git a/package.json b/package.json
index e353843..a9b3d96 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"class-variance-authority": "^0.7.0",
"clipboard-copy": "^4.0.1",
"clsx": "^2.1.0",
+ "cmdk": "^1.0.0",
"fuse.js": "^7.0.0",
"lucide-react": "^0.372.0",
"mcutils-library": "^1.2.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c99f73..89292e7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -71,6 +71,9 @@ dependencies:
clsx:
specifier: ^2.1.0
version: 2.1.0
+ cmdk:
+ specifier: ^1.0.0
+ version: 1.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
fuse.js:
specifier: ^7.0.0
version: 7.0.0
@@ -3016,6 +3019,21 @@ packages:
engines: {node: '>=6'}
dev: false
+ /cmdk@1.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ dependencies:
+ '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ dev: false
+
/co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
diff --git a/src/app/(pages)/docs/[[...slug]]/page.tsx b/src/app/(pages)/docs/[[...slug]]/page.tsx
index d471d52..5d8e66b 100644
--- a/src/app/(pages)/docs/[[...slug]]/page.tsx
+++ b/src/app/(pages)/docs/[[...slug]]/page.tsx
@@ -13,6 +13,7 @@ import {
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { notFound } from "next/navigation";
import { GithubLink } from "@/app/components/docs/github-link";
+import { CommandMenu } from "@/app/components/command-menu";
type DocumentationPageParams = {
params: {
@@ -87,7 +88,9 @@ export default function Page({ params: { slug } }: DocumentationPageParams) {
-
+
+
+
{/* The documentation page title and description */}
diff --git a/src/app/(pages)/docs/layout.tsx b/src/app/(pages)/docs/layout.tsx
deleted file mode 100644
index 339cdb0..0000000
--- a/src/app/(pages)/docs/layout.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React, { ReactElement } from "react";
-import { Sidebar } from "@/app/components/docs/sidebar";
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>): ReactElement {
- return (
-
-
- {children}
-
- );
-}
diff --git a/src/app/common/documentation.ts b/src/app/common/documentation.ts
index cb993d8..b7a67fc 100644
--- a/src/app/common/documentation.ts
+++ b/src/app/common/documentation.ts
@@ -119,7 +119,7 @@ export function getDocContent(path?: string[]): DocsContentMetadata | undefined
* @param limit the maximum number of results
*/
export function searchDocs(
- query: string,
+ query?: string,
limit?: number,
): {
title: string;
@@ -129,7 +129,7 @@ export function searchDocs(
if (!limit) {
limit = 5; // Default to 5 results
}
- return fuseIndex.search(query, { limit }).map(result => {
+ return fuseIndex.search(query || "", { limit }).map(result => {
return {
title: result.item.title,
summary: result.item.summary,
diff --git a/src/app/components/command-menu.tsx b/src/app/components/command-menu.tsx
new file mode 100644
index 0000000..71587db
--- /dev/null
+++ b/src/app/components/command-menu.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import React, { ReactElement, useState } from "react";
+import { DocsContentMetadata } from "@/app/common/documentation";
+import {
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/app/components/ui/command";
+import { Button, ButtonProps } from "@/app/components/ui/button";
+import { useRouter } from "next/navigation";
+import { cn } from "@/app/common/utils";
+
+export function CommandMenu({ ...props }: ButtonProps): ReactElement {
+ const router = useRouter();
+
+ /**
+ * Whether to show the search
+ */
+ const [open, setOpen] = useState(false);
+
+ /**
+ * The pages that were found
+ */
+ const [pages, setPages] = useState(undefined);
+
+ // Handle keyboard shortcuts
+ React.useEffect(() => {
+ const down = (e: KeyboardEvent) => {
+ if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
+ if (
+ (e.target instanceof HTMLElement && e.target.isContentEditable) ||
+ e.target instanceof HTMLInputElement ||
+ e.target instanceof HTMLTextAreaElement ||
+ e.target instanceof HTMLSelectElement
+ ) {
+ return;
+ }
+
+ e.preventDefault();
+ setOpen(open => !open);
+ }
+ };
+
+ return () => document.removeEventListener("keydown", down);
+ }, []);
+
+ /**
+ * Search the documentation
+ * for the given query.
+ *
+ * @param query the query to search for
+ */
+ async function searchDocs(query: string): Promise {
+ // Don't bother searching if the query is less than 3 characters
+ if (query.length < 3) {
+ setPages(undefined);
+ return;
+ }
+
+ // Attempt to search for the query
+ const response = await fetch(`/api/docs/search?query=${query}`);
+ const pages: DocsContentMetadata[] = await response.json();
+ setPages(pages);
+ }
+
+ return (
+ <>
+
+
+
+ {
+ await searchDocs(search);
+ }}
+ />
+
+ No results found.
+ {pages && (
+
+ {pages.map(page => {
+ return (
+ {
+ router.push(`/docs/${page.slug}`);
+ setOpen(false);
+ }}
+ >
+ {page.title}
+
+ );
+ })}
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/app/components/docs/github-link.tsx b/src/app/components/docs/github-link.tsx
index 78a7fb6..abe5ed3 100644
--- a/src/app/components/docs/github-link.tsx
+++ b/src/app/components/docs/github-link.tsx
@@ -16,7 +16,7 @@ export function GithubLink({ page }: GithubLink): ReactElement {
href={`https://git.fascinated.cc/MinecraftUtilities/Frontend/src/branch/master/documentation/${page.slug}.md`}
target="_blank"
>
-
+
);
}
diff --git a/src/app/components/docs/search.tsx b/src/app/components/docs/search.tsx
deleted file mode 100644
index 1105650..0000000
--- a/src/app/components/docs/search.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-"use client";
-
-import React, { ReactElement, useState } from "react";
-import { Button } from "@/app/components/ui/button";
-import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/app/components/ui/dialog";
-import { DocsContentMetadata } from "@/app/common/documentation";
-import { DocumentationPages } from "@/app/components/docs/documentation-pages";
-import { SearchIcon } from "lucide-react";
-
-export function Search(): ReactElement {
- /**
- * The pages that were found
- */
- const [pages, setPages] = useState(undefined);
- const [continueTyping, setContinueTyping] = useState(false);
-
- /**
- * Search the documentation
- * for the given query.
- *
- * @param query the query to search for
- */
- async function searchDocs(query: string): Promise {
- // Don't bother searching if the query is less than 3 characters
- if (query.length < 3) {
- if (query.length > 0) {
- setContinueTyping(true);
- } else {
- setContinueTyping(false);
- }
- return setPages(undefined);
- }
-
- // Attempt to search for the query
- const response = await fetch(`/api/docs/search?query=${query}`);
- const pages: DocsContentMetadata[] = await response.json();
- setPages(pages);
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/app/components/docs/sidebar.tsx b/src/app/components/docs/sidebar.tsx
deleted file mode 100644
index aeb5f46..0000000
--- a/src/app/components/docs/sidebar.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Search } from "@/app/components/docs/search";
-import { ReactElement } from "react";
-
-export function Sidebar(): ReactElement {
- return (
-
-
-
- );
-}
diff --git a/src/app/components/github-star.tsx b/src/app/components/github-star.tsx
index e5ce233..d68de6a 100644
--- a/src/app/components/github-star.tsx
+++ b/src/app/components/github-star.tsx
@@ -3,8 +3,16 @@
import { ReactElement, useEffect, useState } from "react";
import { Star } from "lucide-react";
import Link from "next/link";
+import { cn } from "@/app/common/utils";
-export function GithubStar(): ReactElement {
+type GithubStarProps = {
+ /**
+ * The class name for this component.
+ */
+ className?: string;
+};
+
+export function GithubStar({ className }: GithubStarProps): ReactElement {
const [starCount, setStarCount] = useState(0);
const getStarCount = async () => {
@@ -19,7 +27,10 @@ export function GithubStar(): ReactElement {
return (
diff --git a/src/app/components/navbar.tsx b/src/app/components/navbar.tsx
index 9e24def..1989391 100644
--- a/src/app/components/navbar.tsx
+++ b/src/app/components/navbar.tsx
@@ -2,13 +2,14 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { ReactElement } from "react";
+import { ReactElement, useEffect, useState } from "react";
import { HrefButton } from "./href-button";
import Logo from "./logo";
import { ToggleThemeButton } from "./theme-toggle-button";
import { GithubStar } from "@/app/components/github-star";
import { Card } from "@/app/components/card";
import { cn } from "@/app/common/utils";
+import { CommandMenu } from "@/app/components/command-menu";
type Page = {
/**
@@ -42,7 +43,8 @@ const pages: Page[] = [
];
export default function NavBar(): ReactElement {
- const path = usePathname();
+ const path: string = usePathname();
+ const isDocs: boolean = path ? path.includes("/docs") : false;
return (
{/* Left */}
-
+
-
Minecraft Utilities
+
+ {/* Command Menu */}
+
{/* Links */}
-
+
{pages.map((page, index) => {
const isActive: boolean = path ? path.includes(page.url) : false;
@@ -79,7 +83,7 @@ export default function NavBar(): ReactElement {
{/* Right */}
-
+
);
diff --git a/src/app/components/ui/command.tsx b/src/app/components/ui/command.tsx
new file mode 100644
index 0000000..ad3d6d8
--- /dev/null
+++ b/src/app/components/ui/command.tsx
@@ -0,0 +1,135 @@
+"use client";
+
+import * as React from "react";
+import { type DialogProps } from "@radix-ui/react-dialog";
+import { Command as CommandPrimitive } from "cmdk";
+import { Search } from "lucide-react";
+
+import { cn } from "@/app/common/utils";
+import { Dialog, DialogContent } from "@/app/components/ui/dialog";
+
+const Command = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+Command.displayName = CommandPrimitive.displayName;
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+
+ );
+};
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+));
+
+CommandInput.displayName = CommandPrimitive.Input.displayName;
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+CommandList.displayName = CommandPrimitive.List.displayName;
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => );
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName;
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+
+CommandItem.displayName = CommandPrimitive.Item.displayName;
+
+const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => {
+ return ;
+};
+CommandShortcut.displayName = "CommandShortcut";
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+};
diff --git a/src/app/globals.css b/src/app/globals.css
index 99825d5..05edfe4 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,51 +2,51 @@
@tailwind components;
@tailwind utilities;
+
@layer base {
:root {
--background: 0 0% 100%;
- --foreground: 240 10% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 240 10% 3.9%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 95%;
+ --card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
- --popover-foreground: 240 10% 3.9%;
- --primary: 221.2 83.2% 53.3%;
- --primary-foreground: 355.7 100% 97.3%;
- --secondary: 5 5% 95%;
- --secondary-foreground: 240 5.9% 10%;
- --muted: 240 4.8% 95.9%;
- --muted-foreground: 240 3.8% 46.1%;
- --accent: 240 4.8% 95.9%;
- --accent-foreground: 240 5.9% 10%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
- --border: 240 5.9% 90%;
- --input: 240 5.9% 90%;
- --ring: 221.2 83.2% 53.3%;
- --radius: 0.5rem;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --radius: 0.3rem;
}
.dark {
- --background: 20 14.3% 6.5%;
- --background-accent: 20 14.3% 8.5%;
- --foreground: 0 0% 95%;
- --card: 24 9.8% 10%;
- --card-foreground: 0 0% 95%;
- --popover: 0 0% 9%;
- --popover-foreground: 0 0% 95%;
- --primary: 217.2 91.2% 59.8%;
- --primary-foreground: 144.9 80.4% 10%;
- --secondary: 240 3.7% 15.9%;
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 8%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
- --muted: 0 0% 15%;
- --muted-foreground: 240 5% 64.9%;
- --accent: 12 6.5% 75%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 85.7% 97.3%;
- --border: 240 5.9% 30%;
- --input: 240 3.7% 15.9%;
- --ring: 224.3 76.3% 48%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
}
}