mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Merge branch 'main' into cloudflare
This commit is contained in:
commit
b43ce6a4bf
@ -82,12 +82,20 @@ export default function ServerListClient() {
|
|||||||
? sortedServers
|
? sortedServers
|
||||||
: sortedServers.filter((server) => server.tag === tag);
|
: sortedServers.filter((server) => server.tag === tag);
|
||||||
|
|
||||||
|
const tagCountMap: Record<string, number> = {};
|
||||||
|
sortedServers.forEach((server) => {
|
||||||
|
if (server.tag) {
|
||||||
|
tagCountMap[server.tag] = (tagCountMap[server.tag] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
||||||
<Switch
|
<Switch
|
||||||
allTag={uniqueTags}
|
allTag={uniqueTags}
|
||||||
nowTag={tag}
|
nowTag={tag}
|
||||||
|
tagCountMap={tagCountMap}
|
||||||
onTagChange={handleTagChange}
|
onTagChange={handleTagChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -9,16 +9,17 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { localeItems } from "@/i18n-metadata";
|
import { localeItems } from "@/i18n-metadata";
|
||||||
import { setUserLocale } from "@/i18n/locale";
|
import { setUserLocale } from "@/i18n/locale";
|
||||||
|
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||||
import { useLocale } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export function LanguageSwitcher() {
|
export function LanguageSwitcher() {
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
|
||||||
function onChange(value: string) {
|
const handleSelect = (e: Event, newLocale: string) => {
|
||||||
const locale = value;
|
e.preventDefault(); // 阻止默认的关闭行为
|
||||||
setUserLocale(locale);
|
setUserLocale(newLocale);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -32,10 +33,15 @@ export function LanguageSwitcher() {
|
|||||||
<span className="sr-only">Change language</span>
|
<span className="sr-only">Change language</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent className="flex flex-col gap-0.5" align="end">
|
||||||
{localeItems.map((item) => (
|
{localeItems.map((item) => (
|
||||||
<DropdownMenuItem key={item.code} onClick={() => onChange(item.code)}>
|
<DropdownMenuItem
|
||||||
{item.name}
|
key={item.code}
|
||||||
|
onSelect={(e) => handleSelect(e, item.code)}
|
||||||
|
className={locale === item.code ? "bg-muted gap-3" : ""}
|
||||||
|
>
|
||||||
|
{item.name}{" "}
|
||||||
|
{locale === item.code && <CheckCircleIcon className="size-4" />}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
@ -8,10 +8,12 @@ import React, { createRef, useEffect, useRef } from "react";
|
|||||||
export default function Switch({
|
export default function Switch({
|
||||||
allTag,
|
allTag,
|
||||||
nowTag,
|
nowTag,
|
||||||
|
tagCountMap,
|
||||||
onTagChange,
|
onTagChange,
|
||||||
}: {
|
}: {
|
||||||
allTag: string[];
|
allTag: string[];
|
||||||
nowTag: string;
|
nowTag: string;
|
||||||
|
tagCountMap: Record<string, number>;
|
||||||
onTagChange: (tag: string) => void;
|
onTagChange: (tag: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
@ -84,9 +86,14 @@ export default function Switch({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="relative z-20 flex items-center gap-1">
|
<div className="relative z-20 flex items-center gap-1">
|
||||||
<p className="whitespace-nowrap">
|
<div className="whitespace-nowrap flex items-center gap-2">
|
||||||
{tag === "defaultTag" ? t("defaultTag") : tag}
|
{tag === "defaultTag" ? t("defaultTag") : tag}{" "}
|
||||||
</p>
|
{tag !== "defaultTag" && (
|
||||||
|
<div className="w-fit px-1.5 rounded-full bg-muted">
|
||||||
|
{tagCountMap[tag]}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -7,14 +7,21 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
export function ModeToggle() {
|
export function ModeToggle() {
|
||||||
const { setTheme } = useTheme();
|
const { setTheme, theme } = useTheme();
|
||||||
const t = useTranslations("ThemeSwitcher");
|
const t = useTranslations("ThemeSwitcher");
|
||||||
|
|
||||||
|
const handleSelect = (e: Event, newTheme: string) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setTheme(newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@ -28,15 +35,27 @@ export function ModeToggle() {
|
|||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent className="flex flex-col gap-0.5" align="end">
|
||||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
<DropdownMenuItem
|
||||||
{t("Light")}
|
className={cn({ "gap-3 bg-muted": theme === "light" })}
|
||||||
|
onSelect={(e) => handleSelect(e, "light")}
|
||||||
|
>
|
||||||
|
{t("Light")}{" "}
|
||||||
|
{theme === "light" && <CheckCircleIcon className="size-4" />}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
<DropdownMenuItem
|
||||||
{t("Dark")}
|
className={cn({ "gap-3 bg-muted": theme === "dark" })}
|
||||||
|
onSelect={(e) => handleSelect(e, "dark")}
|
||||||
|
>
|
||||||
|
{t("Dark")}{" "}
|
||||||
|
{theme === "dark" && <CheckCircleIcon className="size-4" />}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
<DropdownMenuItem
|
||||||
{t("System")}
|
className={cn({ "gap-3 bg-muted": theme === "system" })}
|
||||||
|
onSelect={(e) => handleSelect(e, "system")}
|
||||||
|
>
|
||||||
|
{t("System")}{" "}
|
||||||
|
{theme === "system" && <CheckCircleIcon className="size-4" />}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -64,7 +64,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg shadow-neutral-200/50 dark:shadow-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md border bg-popover p-1.5 text-popover-foreground shadow-2xl dark:shadow-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -82,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-xs font-medium outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-[10px] px-2 py-1.5 text-xs font-medium outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nezha-dash",
|
"name": "nezha-dash",
|
||||||
"version": "1.2.8",
|
"version": "1.2.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3020",
|
"dev": "next dev -p 3020",
|
||||||
@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
|
"@heroicons/react": "^2.1.5",
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||||
@ -19,10 +20,10 @@
|
|||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
||||||
"caniuse-lite": "^1.0.30001680",
|
"caniuse-lite": "^1.0.30001680",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -34,7 +35,7 @@
|
|||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"next": "^15.0.3",
|
"next": "^15.0.3",
|
||||||
"next-auth": "^5.0.0-beta.25",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"next-intl": "^3.25.0",
|
"next-intl": "^3.25.1",
|
||||||
"next-runtime-env": "^3.2.2",
|
"next-runtime-env": "^3.2.2",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"react": "19.0.0-rc-02c0e824-20241028",
|
"react": "19.0.0-rc-02c0e824-20241028",
|
||||||
@ -47,7 +48,7 @@
|
|||||||
"swr": "^2.2.6-beta.4",
|
"swr": "^2.2.6-beta.4",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript-eslint": "^8.13.0"
|
"typescript-eslint": "^8.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-turbo": "^2.2.3",
|
"eslint-plugin-turbo": "^2.2.3",
|
||||||
@ -59,7 +60,7 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^15.0.3",
|
||||||
"postcss": "^8.4.48",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
|
Loading…
Reference in New Issue
Block a user