feat: 全站 PageShell 设计系统与专业 UI 统一
- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
1 parent
1acb0c49af
commit
e363ded445
213 files changed
+7148
-6818
No files matched your search
@@ -13,11 +13,11 @@ const buttonVariants = cva(
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
"border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] text-[var(--dg-text-primary)] shadow-xs hover:bg-[var(--dg-bg-hover)] hover:border-[var(--dg-border-hover)]",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
"bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-primary)] border border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
"text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -68,15 +68,15 @@ export function ConfirmProvider({ children }: { children: ReactNode }) {
|
||||
<ConfirmContext.Provider value={{ confirm }}>
|
||||
{children}
|
||||
<Dialog open={open} onOpenChange={(v) => { if (!v) handleCancel(); }}>
|
||||
<DialogContent className="max-w-sm bg-slate-900 border-slate-800" onCloseAutoFocus={(e) => e.preventDefault()}>
|
||||
<DialogContent className="max-w-sm dg-card" onCloseAutoFocus={(e) => e.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-slate-100">{options.title}</DialogTitle>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">{options.title}</DialogTitle>
|
||||
{options.description && (
|
||||
<DialogDescription className="text-slate-400">{options.description}</DialogDescription>
|
||||
<DialogDescription className="dg-text-secondary">{options.description}</DialogDescription>
|
||||
)}
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2 sm:gap-2">
|
||||
<Button variant="outline" className="border-slate-700 text-slate-300" onClick={handleCancel}>
|
||||
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={handleCancel}>
|
||||
{options.cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -64,7 +64,7 @@ export function CopyButton({
|
||||
aria-label={title}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className={cn('text-emerald-400', children ? 'h-3.5 w-3.5 mr-1' : 'h-3.5 w-3.5')} />
|
||||
<Check className={cn('dg-text-green', children ? 'h-3.5 w-3.5 mr-1' : 'h-3.5 w-3.5')} />
|
||||
) : (
|
||||
<Copy className={cn(children ? 'h-3.5 w-3.5 mr-1' : 'h-3.5 w-3.5')} />
|
||||
)}
|
||||
|
||||
@@ -493,9 +493,9 @@ export function DataTable<T = Record<string, unknown>>(
|
||||
{canSort && (
|
||||
<span className="flex-shrink-0">
|
||||
{isSorted && sortDir === 'asc' ? (
|
||||
<ArrowUp className="h-3 w-3 text-blue-400" />
|
||||
<ArrowUp className="h-3 w-3 dg-text-blue" />
|
||||
) : isSorted && sortDir === 'desc' ? (
|
||||
<ArrowDown className="h-3 w-3 text-blue-400" />
|
||||
<ArrowDown className="h-3 w-3 dg-text-blue" />
|
||||
) : (
|
||||
<ArrowUpDown className="h-3 w-3 opacity-40" />
|
||||
)}
|
||||
@@ -582,7 +582,7 @@ export function DataTable<T = Record<string, unknown>>(
|
||||
</span>
|
||||
{selectable && (selectedIds?.length ?? 0) > 0 && (
|
||||
<span className="text-sm text-[var(--dg-text-muted)]">
|
||||
/ 已选 <span className="text-blue-400 font-medium">{selectedIds!.length}</span>
|
||||
/ 已选 <span className="dg-text-blue font-medium">{selectedIds!.length}</span>
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
|
||||
@@ -36,7 +36,7 @@ function DialogOverlay({
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-[var(--dg-overlay)] backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -4,16 +4,16 @@ import {
|
||||
Loader2Icon,
|
||||
OctagonXIcon,
|
||||
TriangleAlertIcon,
|
||||
} from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
} from 'lucide-react';
|
||||
import { Toaster as Sonner, type ToasterProps } from 'sonner';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
const { isDark } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
className="toaster group"
|
||||
icons={{
|
||||
success: <CircleCheckIcon className="size-4" />,
|
||||
@@ -22,17 +22,22 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
error: <OctagonXIcon className="size-4" />,
|
||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||
}}
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast: 'dg-card border-[var(--border-default)] text-[var(--text-primary)]',
|
||||
},
|
||||
}}
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
"--border-radius": "var(--radius)",
|
||||
'--normal-bg': 'var(--surface-raised)',
|
||||
'--normal-text': 'var(--text-primary)',
|
||||
'--normal-border': 'var(--border-default)',
|
||||
'--border-radius': 'var(--radius-control)',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster }
|
||||
export { Toaster };
|
||||
@@ -1,11 +1,11 @@
|
||||
// 紧凑型统计条 — 替代 4~5 个大卡片的笨重布局
|
||||
// 一行显示所有统计项,带颜色区分
|
||||
// 一行显示所有统计项,颜色由主题 CSS 变量驱动 // Logan.Li
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StatItem {
|
||||
label: string;
|
||||
value: number | string;
|
||||
color?: string; // tailwind text class or hex
|
||||
color?: 'blue' | 'emerald' | 'green' | 'amber' | 'red' | 'purple' | 'cyan' | 'muted' | string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -14,14 +14,15 @@ interface StatStripProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const colorMap: Record<string, { text: string; bg: string }> = {
|
||||
blue: { text: 'text-blue-400', bg: 'bg-blue-500/20' },
|
||||
emerald: { text: 'text-emerald-400', bg: 'bg-emerald-500/20' },
|
||||
amber: { text: 'text-amber-400', bg: 'bg-amber-500/20' },
|
||||
red: { text: 'text-red-400', bg: 'bg-red-500/20' },
|
||||
purple: { text: 'text-purple-400', bg: 'bg-purple-500/20' },
|
||||
cyan: { text: 'text-cyan-400', bg: 'bg-cyan-500/20' },
|
||||
muted: { text: 'text-[var(--dg-text-secondary)]', bg: 'bg-[var(--dg-bg-tertiary)]' },
|
||||
const colorMap: Record<string, { text: string; dot: string }> = {
|
||||
blue: { text: 'dg-text-blue', dot: 'var(--dg-blue)' },
|
||||
emerald: { text: 'dg-text-green', dot: 'var(--dg-green)' },
|
||||
green: { text: 'dg-text-green', dot: 'var(--dg-green)' },
|
||||
amber: { text: 'dg-text-yellow', dot: 'var(--dg-yellow)' },
|
||||
red: { text: 'dg-text-red', dot: 'var(--dg-red)' },
|
||||
purple: { text: 'dg-text-purple', dot: 'var(--dg-purple)' },
|
||||
cyan: { text: 'dg-text-cyan', dot: 'var(--dg-cyan)' },
|
||||
muted: { text: 'text-[var(--dg-text-primary)]', dot: 'var(--dg-text-muted)' },
|
||||
};
|
||||
|
||||
export function StatStrip({ items, className }: StatStripProps) {
|
||||
@@ -39,17 +40,15 @@ export function StatStrip({ items, className }: StatStripProps) {
|
||||
{i > 0 && (
|
||||
<span className="w-px h-5 bg-[var(--dg-border)] mx-1 shrink-0" />
|
||||
)}
|
||||
<span className={cn(
|
||||
'w-1.5 h-1.5 rounded-full shrink-0',
|
||||
clr.bg.replace('/20', '').replace('bg-', 'bg-') || 'bg-slate-500',
|
||||
)}
|
||||
style={{ backgroundColor: item.color?.startsWith('#') ? item.color : undefined }}
|
||||
<span
|
||||
className="w-1.5 h-1.5 rounded-full shrink-0"
|
||||
style={{ backgroundColor: item.color?.startsWith('#') ? item.color : clr.dot }}
|
||||
/>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">{item.label}</span>
|
||||
<span className="text-xs dg-text-muted">{item.label}</span>
|
||||
<span className={cn('text-sm font-semibold', clr.text)}>{item.value}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function Switch({
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
"bg-[var(--dg-bg-primary)] pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
|
||||
Reference in new issue
Block a user