diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx
index 00fb43d..268b082 100644
--- a/app/(main)/layout.tsx
+++ b/app/(main)/layout.tsx
@@ -2,6 +2,7 @@ import Footer from "@/app/(main)/footer"
import Header from "@/app/(main)/header"
import { ServerDataProvider } from "@/app/context/server-data-context"
import { auth } from "@/auth"
+import { DashCommand } from "@/components/DashCommand"
import { SignIn } from "@/components/SignIn"
import getEnv from "@/lib/env-entry"
import type React from "react"
@@ -15,7 +16,10 @@ export default function MainLayout({ children }: DashboardProps) {
- {children}
+
+
+ {children}
+
diff --git a/app/layout.tsx b/app/layout.tsx
index b595453..82d703d 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,5 @@
import { FilterProvider } from "@/app/context/network-filter-context"
import { StatusProvider } from "@/app/context/status-context"
-// @auto-i18n-check. Please do not delete the line.
import { ThemeColorManager } from "@/components/ThemeColorManager"
import getEnv from "@/lib/env-entry"
import { cn } from "@/lib/utils"
diff --git a/bun.lockb b/bun.lockb
index 2f0b772..88388b7 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/DashCommand.tsx b/components/DashCommand.tsx
new file mode 100644
index 0000000..7595f80
--- /dev/null
+++ b/components/DashCommand.tsx
@@ -0,0 +1,137 @@
+"use client"
+
+import { Home, Languages, Moon, Sun, SunMoon } from "lucide-react"
+
+import { useServerData } from "@/app/context/server-data-context"
+import {
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command"
+import { localeItems } from "@/i18n-metadata"
+import { setUserLocale } from "@/i18n/locale"
+import { useTranslations } from "next-intl"
+import { useTheme } from "next-themes"
+import { useRouter } from "next/navigation"
+import { useEffect, useState } from "react"
+
+export function DashCommand() {
+ const [open, setOpen] = useState(false)
+ const [search, setSearch] = useState("")
+ const { data } = useServerData()
+ const router = useRouter()
+ const { setTheme } = useTheme()
+ const t = useTranslations("DashCommand")
+
+ 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)
+ }, [])
+
+ if (!data?.result) return null
+
+ const sortedServers = data.result.sort((a, b) => {
+ const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0)
+ if (displayIndexDiff !== 0) return displayIndexDiff
+ return a.id - b.id
+ })
+
+ // 添加语言切换快捷方式
+ const languageShortcuts = localeItems.map((item) => ({
+ keywords: ["language", "locale", item.code.toLowerCase()],
+ icon: ,
+ label: item.name,
+ action: () => setUserLocale(item.code),
+ value: `language ${item.name.toLowerCase()} ${item.code}`,
+ }))
+
+ // 将语言快捷方式添加到现有的shortcuts数组中
+ const shortcuts = [
+ {
+ keywords: ["home", "homepage"],
+ icon: ,
+ label: t("Home"),
+ action: () => router.push("/"),
+ },
+ {
+ keywords: ["light", "theme", "lightmode"],
+ icon: ,
+ label: t("ToggleLightMode"),
+ action: () => setTheme("light"),
+ },
+ {
+ keywords: ["dark", "theme", "darkmode"],
+ icon: ,
+ label: t("ToggleDarkMode"),
+ action: () => setTheme("dark"),
+ },
+ {
+ keywords: ["system", "theme", "systemmode"],
+ icon: ,
+ label: t("ToggleSystemMode"),
+ action: () => setTheme("system"),
+ },
+ ...languageShortcuts,
+ ].map((item) => ({
+ ...item,
+ value: `${item.keywords.join(" ")} ${item.label}`,
+ }))
+
+ return (
+ <>
+
+
+
+ {t("NoResults")}
+
+ {sortedServers.map((server) => (
+ {
+ router.push(`/server/${server.id}`)
+ setOpen(false)
+ }}
+ >
+ {server.online_status ? (
+
+ ) : (
+
+ )}
+ {server.name}
+
+ ))}
+
+
+
+
+ {shortcuts.map((item) => (
+ {
+ item.action()
+ setOpen(false)
+ }}
+ >
+ {item.icon}
+ {item.label}
+
+ ))}
+
+
+
+ >
+ )
+}
diff --git a/components/ui/command.tsx b/components/ui/command.tsx
new file mode 100644
index 0000000..d29ada4
--- /dev/null
+++ b/components/ui/command.tsx
@@ -0,0 +1,147 @@
+"use client"
+
+import { type DialogProps, DialogTitle } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+import * as React from "react"
+
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+import { cn } from "@/lib/utils"
+
+const Command = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+const CommandDialog = ({ children, ...props }: DialogProps) => {
+ 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/components/ui/dialog.tsx b/components/ui/dialog.tsx
new file mode 100644
index 0000000..fbd7e86
--- /dev/null
+++ b/components/ui/dialog.tsx
@@ -0,0 +1,104 @@
+"use client"
+
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/messages/en.json b/messages/en.json
index f1d1edf..03872bf 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -125,5 +125,15 @@
"h1_490-590_404NotFound": "Server Not Found",
"h1_490-590_404NotFoundBack": " Press here to go back",
"h1_490-590_Error": "Something went wrong"
+ },
+ "DashCommand": {
+ "TypeCommand": "Type a command or search...",
+ "NoResults": "No results found.",
+ "Servers": "Servers",
+ "Shortcuts": "Shortcuts",
+ "ToggleLightMode": "Toggle Light Mode",
+ "ToggleDarkMode": "Toggle Dark Mode",
+ "ToggleSystemMode": "Toggle System Mode",
+ "Home": "Home"
}
}
diff --git a/messages/ja.json b/messages/ja.json
index 76fb128..c4580b7 100644
--- a/messages/ja.json
+++ b/messages/ja.json
@@ -125,5 +125,15 @@
"h1_490-590_404NotFound": "サーバーは見つかりません",
"h1_490-590_404NotFoundBack": "戻る",
"h1_490-590_Error": "何らかの問題が発生しました"
+ },
+ "DashCommand": {
+ "TypeCommand": "コマンドを入力してください",
+ "NoResults": "結果は見つかりませんでした",
+ "Servers": "サーバー",
+ "Shortcuts": "ショートカット",
+ "ToggleLightMode": "ライトモードに切り替え",
+ "ToggleDarkMode": "ダークモードに切り替え",
+ "ToggleSystemMode": "システムモードに切り替え",
+ "Home": "ホーム"
}
}
diff --git a/messages/zh-t.json b/messages/zh-t.json
index 477ae3f..8fb39ab 100644
--- a/messages/zh-t.json
+++ b/messages/zh-t.json
@@ -125,5 +125,15 @@
"h1_490-590_404NotFound": "伺服器未找到",
"h1_490-590_404NotFoundBack": "返回",
"h1_490-590_Error": "發生錯誤"
+ },
+ "DashCommand": {
+ "TypeCommand": "輸入命令或搜尋",
+ "NoResults": "沒有結果",
+ "Servers": "伺服器",
+ "Shortcuts": "快捷鍵",
+ "ToggleLightMode": "切換亮色模式",
+ "ToggleDarkMode": "切換暗色模式",
+ "ToggleSystemMode": "切換系統模式",
+ "Home": "首頁"
}
}
diff --git a/messages/zh.json b/messages/zh.json
index 6346934..0ed66f0 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -125,5 +125,15 @@
"h1_490-590_404NotFound": "服务器不存在",
"h1_490-590_404NotFoundBack": "返回",
"h1_490-590_Error": "发生错误"
+ },
+ "DashCommand": {
+ "TypeCommand": "输入命令或搜索",
+ "NoResults": "结果为空",
+ "Servers": "服务器",
+ "Shortcuts": "快捷键",
+ "ToggleLightMode": "切换亮色模式",
+ "ToggleDarkMode": "切换暗色模式",
+ "ToggleSystemMode": "切换系统模式",
+ "Home": "首页"
}
}
diff --git a/package.json b/package.json
index ec83ceb..1242742 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"caniuse-lite": "^1.0.30001695",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "cmdk": "1.0.0",
"country-flag-icons": "^1.5.14",
"d3-geo": "^3.1.1",
"d3-selection": "^3.0.0",
@@ -65,11 +66,11 @@
"@biomejs/biome": "1.9.4",
"@next/bundle-analyzer": "^15.1.6",
"@tailwindcss/postcss": "^4.0.0",
- "@types/node": "^22.10.7",
- "@types/react": "^19.0.7",
+ "@types/node": "^22.10.9",
+ "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"eslint-config-next": "^15.1.6",
- "eslint-plugin-turbo": "^2.3.3",
+ "eslint-plugin-turbo": "^2.3.4",
"eslint-plugin-unused-imports": "^4.1.4",
"postcss": "^8.5.1",
"tailwindcss": "^4.0.0",
@@ -78,6 +79,5 @@
},
"overrides": {
"react-is": "^19.0.0-rc-69d4b800-20241021"
- },
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+ }
}