mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Compare commits
3 Commits
55cec2e055
...
3411783401
Author | SHA1 | Date | |
---|---|---|---|
|
3411783401 | ||
|
1ab1559c20 | ||
|
0837393903 |
@ -21,7 +21,8 @@
|
||||
"useSortedClasses": "error"
|
||||
},
|
||||
"a11y": {
|
||||
"useKeyWithClickEvents": "off"
|
||||
"useKeyWithClickEvents": "off",
|
||||
"noLabelWithoutControl": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "off"
|
||||
|
@ -4,23 +4,28 @@ import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CheckCircleIcon } from "@heroicons/react/20/solid"
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
import { CheckIcon, MinusIcon, Moon, Sun } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useTheme } from "next-themes"
|
||||
import { useId } from "react"
|
||||
import { RadioGroup, RadioGroupItem } from "./ui/radio-group"
|
||||
|
||||
const items = [
|
||||
{ value: "light", label: "Light", image: "/ui-light.png" },
|
||||
{ value: "dark", label: "Dark", image: "/ui-dark.png" },
|
||||
{ value: "system", label: "System", image: "/ui-system.png" },
|
||||
]
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme, theme } = useTheme()
|
||||
const t = useTranslations("ThemeSwitcher")
|
||||
|
||||
const handleSelect = (e: Event, newTheme: string) => {
|
||||
e.preventDefault()
|
||||
const handleSelect = (newTheme: string) => {
|
||||
setTheme(newTheme)
|
||||
}
|
||||
const id = useId()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@ -35,31 +40,40 @@ export function ModeToggle() {
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="flex flex-col gap-0.5" align="end">
|
||||
<DropdownMenuItem
|
||||
className={cn("rounded-b-[5px]", {
|
||||
"gap-3 bg-muted font-semibold": theme === "light",
|
||||
})}
|
||||
onSelect={(e) => handleSelect(e, "light")}
|
||||
>
|
||||
{t("Light")} {theme === "light" && <CheckCircleIcon className="size-4" />}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className={cn("rounded-[5px]", {
|
||||
"gap-3 bg-muted font-semibold": theme === "dark",
|
||||
})}
|
||||
onSelect={(e) => handleSelect(e, "dark")}
|
||||
>
|
||||
{t("Dark")} {theme === "dark" && <CheckCircleIcon className="size-4" />}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className={cn("rounded-t-[5px]", {
|
||||
"gap-3 bg-muted font-semibold": theme === "system",
|
||||
})}
|
||||
onSelect={(e) => handleSelect(e, "system")}
|
||||
>
|
||||
{t("System")} {theme === "system" && <CheckCircleIcon className="size-4" />}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuContent className="px-3 pt-3 pb-2" align="end">
|
||||
<fieldset className="space-y-4">
|
||||
<RadioGroup className="flex gap-3" defaultValue={theme} onValueChange={handleSelect}>
|
||||
{items.map((item) => (
|
||||
<label key={`${id}-${item.value}`}>
|
||||
<RadioGroupItem
|
||||
id={`${id}-${item.value}`}
|
||||
value={item.value}
|
||||
className="peer sr-only after:absolute after:inset-0"
|
||||
/>
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.label}
|
||||
width={88}
|
||||
height={70}
|
||||
className="relative cursor-pointer overflow-hidden rounded-[8px] border border-neutral-300 shadow-xs outline-none transition-[color,box-shadow] peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50 peer-data-disabled:cursor-not-allowed peer-data-[state=checked]:bg-accent peer-data-disabled:opacity-50 dark:border-neutral-700"
|
||||
/>
|
||||
<span className="group mt-2 flex items-center gap-1 peer-data-[state=unchecked]:text-muted-foreground/70">
|
||||
<CheckIcon
|
||||
size={16}
|
||||
className="group-peer-data-[state=unchecked]:hidden"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<MinusIcon
|
||||
size={16}
|
||||
className="group-peer-data-[state=checked]:hidden"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="font-medium text-xs">{t(item.label)}</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</fieldset>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
|
50
components/ui/radio-group.tsx
Normal file
50
components/ui/radio-group.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
import type * as React from "react"
|
||||
|
||||
function RadioGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
data-slot="radio-group"
|
||||
className={cn("grid gap-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function RadioGroupItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
data-slot="radio-group-item"
|
||||
className={cn(
|
||||
"aspect-square size-4 shrink-0 rounded-full border border-input shadow-xs outline-none transition-shadow focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:aria-invalid:ring-destructive/40",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center text-current">
|
||||
<svg
|
||||
role="img"
|
||||
aria-label="Radio indicator"
|
||||
width="6"
|
||||
height="6"
|
||||
viewBox="0 0 6 6"
|
||||
fill="currentcolor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="3" cy="3" r="3" />
|
||||
</svg>
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem }
|
@ -4,7 +4,7 @@ import getEnv from "./lib/env-entry"
|
||||
export const localeItems = [
|
||||
{ code: "en", name: "English" },
|
||||
{ code: "ja", name: "日本語" },
|
||||
{ code: "zh-t", name: "中文繁體" },
|
||||
{ code: "zh-TW", name: "中文繁體" },
|
||||
{ code: "zh", name: "中文简体" },
|
||||
]
|
||||
|
||||
|
23
package.json
23
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nezha-dash",
|
||||
"version": "2.9.1",
|
||||
"version": "2.9.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3040",
|
||||
@ -21,6 +21,7 @@
|
||||
"@radix-ui/react-navigation-menu": "^1.2.5",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.2",
|
||||
"@radix-ui/react-radio-group": "^1.2.3",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.3",
|
||||
@ -28,8 +29,8 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/d3-geo": "^3.1.0",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-e552027-20250112",
|
||||
"@types/luxon": "^3.6.0",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250328",
|
||||
"caniuse-lite": "^1.0.30001707",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@ -41,33 +42,33 @@
|
||||
"flag-icons": "^7.3.2",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"lucide-react": "^0.474.0",
|
||||
"luxon": "^3.6.0",
|
||||
"luxon": "^3.6.1",
|
||||
"maxmind": "^4.3.24",
|
||||
"next": "^15.2.4",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-intl": "^4.0.2",
|
||||
"next-runtime-env": "^3.2.2",
|
||||
"next-runtime-env": "^3.3.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
"react-wrap-balancer": "^1.1.1",
|
||||
"recharts": "^2.15.1",
|
||||
"sharp": "^0.33.5",
|
||||
"swr": "^2.3.3",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwind-merge": "^3.1.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@next/bundle-analyzer": "^15.2.4",
|
||||
"@tailwindcss/postcss": "^4.0.16",
|
||||
"@types/node": "^22.13.13",
|
||||
"@tailwindcss/postcss": "^4.1.0",
|
||||
"@types/node": "^22.13.17",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.16",
|
||||
"tailwindcss": "^4.1.0",
|
||||
"typescript": "^5.8.2",
|
||||
"vercel": "^41.4.1"
|
||||
},
|
||||
|
BIN
public/ui-dark.png
Normal file
BIN
public/ui-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 364 B |
BIN
public/ui-light.png
Normal file
BIN
public/ui-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 370 B |
BIN
public/ui-system.png
Normal file
BIN
public/ui-system.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 414 B |
Loading…
Reference in New Issue
Block a user