Files
sundynix-micro-admin/src/components/CommandPalette.tsx
T
2026-04-30 22:53:46 +08:00

107 lines
6.1 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Command } from 'cmdk'
import { useNavigate } from 'react-router-dom'
import { Search, Monitor, Moon, Sun, Laptop } from 'lucide-react'
import { useAppStore } from '@/store/app'
import { useAuthStore } from '@/store/auth'
import './cmdk.css' // We'll add some styles for cmdk
export default function CommandPalette() {
const { cmdKOpen, setCmdKOpen, setThemeHue } = useAppStore()
const menus = useAuthStore(s => s.menus)
const navigate = useNavigate()
const [, setTheme] = useState(document.documentElement.classList.contains('dark') ? 'dark' : 'light')
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setCmdKOpen(!cmdKOpen)
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [cmdKOpen, setCmdKOpen])
const runCommand = (command: () => void) => {
setCmdKOpen(false)
command()
}
const toggleTheme = (mode: 'light' | 'dark' | 'system') => {
if (mode === 'dark') {
document.documentElement.classList.add('dark')
setTheme('dark')
} else if (mode === 'light') {
document.documentElement.classList.remove('dark')
setTheme('light')
} else {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark')
setTheme('dark')
} else {
document.documentElement.classList.remove('dark')
setTheme('light')
}
}
}
// Flatten menus for search
const flatMenus: { title: string, path: string }[] = []
const flatten = (items: any[]) => {
items?.forEach(i => {
if (i.path && !i.children?.length && i.path !== '/dashboard') {
flatMenus.push({ title: i.title || i.name, path: i.path })
}
if (i.children) flatten(i.children)
})
}
if (menus) flatten(menus)
return (
<Command.Dialog open={cmdKOpen} onOpenChange={setCmdKOpen} label="Global Command Menu" className="cmdk-dialog glass-panel">
<div className="flex items-center px-4 border-b border-white/10 dark:border-white/5">
<Search className="w-5 h-5 text-muted-foreground mr-3" />
<Command.Input placeholder="搜索菜单,或输入命令..." className="w-full bg-transparent border-0 h-14 text-sm outline-none focus:outline-none focus-visible:ring-0 focus-visible:outline-none placeholder:text-muted-foreground text-foreground" />
</div>
<Command.List className="max-h-[300px] overflow-y-auto p-2 scrollbar-none">
<Command.Empty className="py-6 text-center text-sm text-muted-foreground">.</Command.Empty>
<Command.Group heading="页面导航" className="text-xs font-medium text-muted-foreground mb-1 px-2 py-1">
<Command.Item onSelect={() => runCommand(() => navigate('/dashboard'))} className="flex items-center px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-colors aria-selected:bg-primary/10 aria-selected:text-primary text-foreground mt-1">
<Monitor className="w-4 h-4 mr-3" />
</Command.Item>
{flatMenus.map(m => (
<Command.Item key={m.path} onSelect={() => runCommand(() => navigate(m.path))} className="flex items-center px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-colors aria-selected:bg-primary/10 aria-selected:text-primary text-foreground mt-1">
<Search className="w-4 h-4 mr-3 opacity-50" /> {m.title}
</Command.Item>
))}
</Command.Group>
<Command.Group heading="主题模式" className="text-xs font-medium text-muted-foreground mt-4 mb-1 px-2 py-1">
<Command.Item onSelect={() => runCommand(() => toggleTheme('light'))} className="flex items-center px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-colors aria-selected:bg-primary/10 aria-selected:text-primary text-foreground mt-1">
<Sun className="w-4 h-4 mr-3" />
</Command.Item>
<Command.Item onSelect={() => runCommand(() => toggleTheme('dark'))} className="flex items-center px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-colors aria-selected:bg-primary/10 aria-selected:text-primary text-foreground mt-1">
<Moon className="w-4 h-4 mr-3" />
</Command.Item>
<Command.Item onSelect={() => runCommand(() => toggleTheme('system'))} className="flex items-center px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-colors aria-selected:bg-primary/10 aria-selected:text-primary text-foreground mt-1">
<Laptop className="w-4 h-4 mr-3" />
</Command.Item>
</Command.Group>
<Command.Group heading="重置主题色" className="text-xs font-medium text-muted-foreground mt-4 mb-1 px-2 py-1">
<div className="flex gap-2 px-3 py-2 mt-1">
<button onClick={() => runCommand(() => setThemeHue('145'))} style={{ backgroundColor: 'oklch(0.55 0.12 145)' }} className="w-6 h-6 rounded-full ring-2 ring-transparent hover:ring-white/20 transition-all"></button>
<button onClick={() => runCommand(() => setThemeHue('240'))} style={{ backgroundColor: 'oklch(0.55 0.12 240)' }} className="w-6 h-6 rounded-full ring-2 ring-transparent hover:ring-white/20 transition-all"></button>
<button onClick={() => runCommand(() => setThemeHue('280'))} style={{ backgroundColor: 'oklch(0.55 0.12 280)' }} className="w-6 h-6 rounded-full ring-2 ring-transparent hover:ring-white/20 transition-all"></button>
<button onClick={() => runCommand(() => setThemeHue('330'))} style={{ backgroundColor: 'oklch(0.55 0.12 330)' }} className="w-6 h-6 rounded-full ring-2 ring-transparent hover:ring-white/20 transition-all"></button>
<button onClick={() => runCommand(() => setThemeHue('45'))} style={{ backgroundColor: 'oklch(0.65 0.18 45)' }} className="w-6 h-6 rounded-full ring-2 ring-transparent hover:ring-white/20 transition-all"></button>
</div>
</Command.Group>
</Command.List>
</Command.Dialog>
)
}