refactor(ui): unify all dialogs on radix base per guide ch.6/7

- base dialog: z raised to 300/301 (was below sidebar z-200 - dialogs
  appeared sunken under navigation), overlay bg-black/45 + 2px blur,
  unified close button with hover/focus-visible states, footer pt-4
- FormDialog normalized: width presets sm/md/lg/xl + optional
  description, single scrollable body (header/footer fixed), unified
  top-right close from base
- ModalOverlay rewritten on radix kernel, same props (+title/noScroll):
  portal/focus trap/scroll lock/ESC with proper nesting (fixes
  chain-close when ticket detail opens quick-review), 200ms animation,
  z-300 layering - was a bare div with no a11y, no animation, ESC
  via document listener that leaked to nested dialogs
- killed box-in-box: all 14 ModalOverlay callers stripped their inner
  card wrapper (double border/shadow/radius/padding gone) and duplicate
  scroll containers; EditInstanceDialog (4th standalone system) migrated
  to ModalOverlay, gains ESC/animation/portal
- visual unification per guide: dialog title text-base semibold
  (was 4 sizes), close button one form (was 5), footer buttons on
  dg-btn system (secondary/primary/danger, was 4 systems), primary
  color var(--dg-blue) everywhere (yellow/emerald/blue-500/600 mixed
  before), widths 4 presets (was 7 arbitrary values)
- dead code removed: 3 orphaned LDAP dialog components, leftover
  create-OU state/handler in BatchImportModal, unused icon imports

verified in browser: users create dialog shows single card (0 inner
cards), 12px radius, z-301/300, overlay blur, sidebar correctly
covered; tsc/build/audit gates green
This commit is contained in:
Logan.Li committed 2026-10-27 16:35:32 +08:00
1 parent 36724bba94
commit 48c8fddbe9
20 files changed
+1645 -1258

No files matched your search

@@ -5,7 +5,6 @@
*/
import { useState } from 'react';
import { XCircle } from 'lucide-react';
import { userApi } from '@/api/user';
import { toast } from '@/hooks/useToast';
import { ModalOverlay } from '@/components/ModalOverlay';
@@ -44,18 +43,20 @@ export function CreateUserModal({ onClose, onCreated }: CreateUserModalProps) {
};
return (
<ModalOverlay open={true} onClose={onClose} width="max-w-md">
<div className="w-full bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-xl p-6 shadow-xl">
<ModalOverlay open={true} onClose={onClose} width="md">
<div className="w-full">
<div className="flex items-center justify-between mb-5">
<h2 className="text-lg font-semibold">新建用户</h2>
<button onClick={onClose} className="text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)] transition-colors"><XCircle className="w-5 h-5" /></button>
<h2 className="text-base font-semibold">新建用户</h2>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">用户名 *</label>
<input
value={username}
onChange={e => { setUsername(e.target.value); if (errors.username) setErrors(p => ({ ...p, username: undefined })); }}
onChange={(e) => {
setUsername(e.target.value);
if (errors.username) setErrors((p) => ({ ...p, username: undefined }));
}}
className={`w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border rounded-lg text-sm focus:border-blue-400 focus:outline-none ${errors.username ? 'border-red-400' : 'border-[var(--dg-border)]'}`}
placeholder="3-30位字母、数字或下划线"
/>
@@ -63,14 +64,22 @@ export function CreateUserModal({ onClose, onCreated }: CreateUserModalProps) {
</div>
<div>
<label className="block text-sm font-medium mb-1">显示名称</label>
<input value={displayName} onChange={e => setDisplayName(e.target.value)} className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none" placeholder="用户的显示名称" />
<input
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
placeholder="用户的显示名称"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">密码 *</label>
<input
type="password"
value={password}
onChange={e => { setPassword(e.target.value); if (errors.password) setErrors(p => ({ ...p, password: undefined })); }}
onChange={(e) => {
setPassword(e.target.value);
if (errors.password) setErrors((p) => ({ ...p, password: undefined }));
}}
className={`w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border rounded-lg text-sm focus:border-blue-400 focus:outline-none ${errors.password ? 'border-red-400' : 'border-[var(--dg-border)]'}`}
placeholder="至少6位"
/>
@@ -81,7 +90,10 @@ export function CreateUserModal({ onClose, onCreated }: CreateUserModalProps) {
<input
type="email"
value={email}
onChange={e => { setEmail(e.target.value); if (errors.email) setErrors(p => ({ ...p, email: undefined })); }}
onChange={(e) => {
setEmail(e.target.value);
if (errors.email) setErrors((p) => ({ ...p, email: undefined }));
}}
className={`w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border rounded-lg text-sm focus:border-blue-400 focus:outline-none ${errors.email ? 'border-red-400' : 'border-[var(--dg-border)]'}`}
placeholder="example@company.com"
/>
@@ -89,7 +101,11 @@ export function CreateUserModal({ onClose, onCreated }: CreateUserModalProps) {
</div>
<div>
<label className="block text-sm font-medium mb-1">角色</label>
<select value={role} onChange={e => setRole(e.target.value)} className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none">
<select
value={role}
onChange={(e) => setRole(e.target.value)}
className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
>
<option value="admin">超级管理员</option>
<option value="dba">数据库管理员</option>
<option value="developer">开发人员</option>
@@ -97,8 +113,10 @@ export function CreateUserModal({ onClose, onCreated }: CreateUserModalProps) {
</select>
</div>
<div className="flex justify-end gap-3 pt-2">
<button onClick={onClose} className="px-4 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm hover:bg-[var(--dg-bg-hover)] transition-colors">取消</button>
<button onClick={handleSubmit} disabled={submitting} className="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:opacity-90 disabled:opacity-50 transition-opacity">
<button onClick={onClose} className="dg-btn dg-btn-secondary dg-btn-sm">
取消
</button>
<button onClick={handleSubmit} disabled={submitting} className="dg-btn dg-btn-primary dg-btn-sm">
{submitting ? '创建中...' : '创建'}
</button>
</div>