feat(ui): 自研 Celadon 组件族入库与 AntD 消费全量替换 - base(Kbd/StatusBadge/IconButton/ToolButton)+product(EmptyState/Callout/SegmentedControl/Checkbox/Select/Tooltip/Switch), composio 视觉基底, globals.css 覆盖清理 (ADR-0008)

This commit is contained in:
maomaochong-ai
2026-09-06 20:28:51 +08:00
parent 0dcd8e1c72
commit c3e2758736
128 changed files with 2791 additions and 737 deletions

View File

@@ -1,4 +1,5 @@
import { App, Button, Form, Input, Select, Skeleton, Switch } from "antd";
import { App, Button, Form, Input, Select, Skeleton } from "antd";
import { Switch } from "@/components/ui/base/switch";
import { AlertTriangle, BadgeCheck, ChevronDown, Globe2, KeyRound, LockKeyhole, RefreshCw, RotateCcw, Save, ShieldCheck, UserPlus, UsersRound } from "lucide-react";
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { useBlocker } from "react-router";

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState, type ChangeEvent } from "react";
import { App, Button, Form, Input, Modal, Select, Switch } from "antd";
import { App, Button, Form, Input, Modal, Select } from "antd";
import { Switch } from "@/components/ui/base/switch";
import type { InputRef } from "antd";
import type { ColumnsType } from "antd/es/table";
import { PencilLine, Pin, Plus, RefreshCw, Search, Send, Upload, X } from "lucide-react";

View File

@@ -1,4 +1,5 @@
import { ConfigProvider, Dropdown, Tooltip } from "antd";
import { ConfigProvider, Dropdown } from "antd";
import { Tooltip } from "@/components/ui/base/tooltip";
import type { MenuProps } from "antd";
import {
ArrowLeft,
@@ -131,7 +132,7 @@ export function AdminShell() {
<main className="admin-shell app-user-workspace flex h-full min-h-0 overflow-hidden text-foreground">
<aside className={cn("app-workspace-sidebar admin-sidebar hidden shrink-0 flex-col overflow-hidden lg:flex", collapsed && "is-collapsed")}>
<div className="admin-sidebar-identity shrink-0">
<Tooltip mouseEnterDelay={0.1} title={collapsed ? "查看更新日志" : undefined} placement="right" rootClassName="app-workspace-sidebar-tooltip">
<Tooltip delay={100} title={collapsed ? "查看更新日志" : undefined} placement="right">
<AppChangelogButton
className={cn("admin-sidebar-brand-button", collapsed && "is-collapsed")}
icon={<BrandLogoFrame className="admin-sidebar-brand-mark grid shrink-0 place-items-center bg-foreground text-background" logoClassName="size-5 object-contain" alt="" fallback={<InfinityIcon className="size-4" />} />}
@@ -145,7 +146,7 @@ export function AdminShell() {
</div>
<AdminNavigation collapsed={collapsed} />
<div className="admin-sidebar-footer shrink-0">
<Tooltip mouseEnterDelay={0.1} title={collapsed ? "返回创作台" : undefined} placement="right" rootClassName="app-workspace-sidebar-tooltip">
<Tooltip delay={100} title={collapsed ? "返回创作台" : undefined} placement="right">
<NavLink
to="/"
aria-label={collapsed ? "返回创作台" : undefined}
@@ -157,7 +158,7 @@ export function AdminShell() {
</Tooltip>
</div>
</aside>
<Tooltip mouseEnterDelay={0.1} title={collapsed ? "展开侧栏" : "收起侧栏"} placement="right" rootClassName="app-workspace-sidebar-tooltip">
<Tooltip delay={100} title={collapsed ? "展开侧栏" : "收起侧栏"} placement="right">
<button type="button" className={cn("admin-sidebar-edge-toggle hidden lg:grid", collapsed && "is-collapsed")} onClick={toggleCollapsed} aria-label={collapsed ? "展开侧栏" : "收起侧栏"} aria-expanded={!collapsed}>
{collapsed ? <ChevronRight className="size-3.5" aria-hidden="true" /> : <ChevronLeft className="size-3.5" aria-hidden="true" />}
</button>
@@ -293,7 +294,7 @@ function AdminNavigation({ collapsed }: { collapsed: boolean }) {
return (
<div key={group.label} className="admin-nav-group">
{!collapsed ? (
<div className="admin-nav-group-label mb-1 px-2.5 text-[var(--fs-tiny)] font-medium text-foreground/38">
<div className="admin-nav-group-label">
<span>{group.label}</span>
</div>
) : (
@@ -301,16 +302,16 @@ function AdminNavigation({ collapsed }: { collapsed: boolean }) {
)}
<div className={cn("admin-nav-group-items space-y-0.5", collapsed && "is-collapsed")}>
{visibleItems.map((item) => (
<Tooltip key={item.path} mouseEnterDelay={0.1} title={collapsed ? item.label : undefined} placement="right" rootClassName="app-workspace-sidebar-tooltip">
<Tooltip key={item.path} delay={100} title={collapsed ? item.label : undefined} placement="right">
<NavLink
to={item.path}
end={item.path === "/admin"}
aria-label={collapsed ? item.label : undefined}
className={({ isActive }) =>
cn(
"app-workspace-nav-link flex h-8 items-center rounded-md text-[var(--fs-body)] transition-colors",
"app-workspace-nav-link flex h-8 items-center rounded-md transition-colors",
collapsed ? "justify-center px-0" : "gap-2.5 px-2.5",
isActive ? "is-active font-medium" : "text-foreground/62 hover:bg-surface-hover hover:text-foreground",
isActive ? "is-active" : "text-foreground/62 hover:bg-surface-hover hover:text-foreground",
)
}
>

View File

@@ -1,3 +1,5 @@
import { StatusBadge } from "@/components/ui/base/badges";
import { EmptyState } from "@/components/ui/product/empty-state";
import { App, Button, Dropdown, Table } from "antd";
import type { ButtonProps, MenuProps, TableProps } from "antd";
import { saveAs } from "file-saver";
@@ -12,12 +14,18 @@ export const configuredSecretText = "已配置 · 留空不改";
export type AdminStatusTone = "neutral" | "success" | "warning" | "error" | "info";
/** admin 状态徽章:归并到自研 StatusBadge(outline 圆点胶囊),admin 全部页面零改动统一视觉。
* tone 归一:info 在 admin 中本就是前景中性(--admin-status-info: var(--foreground))→ neutral。 */
const adminToneToBadgeTone: Record<AdminStatusTone, "neutral" | "success" | "warning" | "error"> = {
neutral: "neutral",
success: "success",
warning: "warning",
error: "error",
info: "neutral",
};
export function AdminStatusBadge({ label, tone = "neutral", title }: { label: string; tone?: AdminStatusTone; title?: string }) {
return (
<span className="admin-status-badge" data-tone={tone} title={title}>
{label}
</span>
);
return <StatusBadge tone={adminToneToBadgeTone[tone]} title={title} label={label} />;
}
export function AdminStatTile({ label, value, detail, trend }: { label: string; value: string | number; detail?: string; trend?: { value: string; tone?: AdminStatusTone } }) {
@@ -133,14 +141,14 @@ export function AdminExportButton({
export function AdminTableEmpty({ filtered = false, title, description, action }: { filtered?: boolean; title?: string; description?: string; action?: ReactNode }) {
return (
<div className="flex min-h-40 flex-col items-center justify-center px-6 py-8 text-center">
<span className="grid size-9 place-items-center rounded-md bg-muted/35 text-foreground/45">
<SearchX className="size-4" />
</span>
<div className="mt-3 text-sm font-medium">{title || (filtered ? "没有符合筛选条件的数据" : "暂无数据")}</div>
{description ? <p className="mt-1 max-w-sm text-xs leading-5 text-foreground/50">{description}</p> : null}
{action ? <div className="mt-4">{action}</div> : null}
</div>
<EmptyState
size="compact"
icon={SearchX}
title={title || (filtered ? "没有符合筛选条件的数据" : "暂无数据")}
description={description}
action={action}
className="min-h-40 w-full justify-center"
/>
);
}

View File

@@ -1,5 +1,7 @@
import { useEffect, useState } from "react";
import { App, Button, Descriptions, Drawer, Empty, Progress, Skeleton, Tabs } from "antd";
import { App, Button, Descriptions, Drawer, Progress, Skeleton, Tabs } from "antd";
import { IconButton } from "@/components/ui/base/buttons";
import { EmptyState } from "@/components/ui/product/empty-state";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { PaginationBar } from "@/components/layout/workspace-page";
@@ -97,8 +99,8 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
rootClassName="admin-drawer"
extra={onNavigate ? (
<div className="flex items-center gap-1">
<Button type="text" size="small" aria-label="上一条用户" disabled={!previousUserId} icon={<ChevronLeft className="size-4" />} onClick={() => previousUserId && onNavigate(previousUserId)} />
<Button type="text" size="small" aria-label="下一条用户" disabled={!nextUserId} icon={<ChevronRight className="size-4" />} onClick={() => nextUserId && onNavigate(nextUserId)} />
<IconButton size="sm" variant="ghost" aria-label="上一条用户" disabled={!previousUserId} icon={ChevronLeft} onClick={() => previousUserId && onNavigate(previousUserId)} />
<IconButton size="sm" variant="ghost" aria-label="下一条用户" disabled={!nextUserId} icon={ChevronRight} onClick={() => nextUserId && onNavigate(nextUserId)} />
</div>
) : null}
>
@@ -225,7 +227,7 @@ export function AdminUserDetailDrawer({ userId, onClose, previousUserId, nextUse
]}
/>
) : (
<Empty description="没有用户详情" />
<EmptyState size="compact" title="没有用户详情" />
)}
</Drawer>
);

View File

@@ -1,5 +1,7 @@
import { App, Button, DatePicker, Drawer, Form, Input, Modal, Select, Tabs, Tag } from "antd";
import { Tooltip } from "@/components/ui/base/tooltip";
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
import { App, Button, DatePicker, Drawer, Form, Input, Modal, Select, Tabs, Tag, Tooltip } from "antd";
import type { ColumnsType } from "antd/es/table";
import dayjs, { type Dayjs } from "dayjs";
import { AlertTriangle, BarChart3, CircleDollarSign, Clock3, Gauge, Pencil, Plus, RefreshCw, Settings2, Trash2, UsersRound, Workflow } from "lucide-react";

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { App, Button, Descriptions, Drawer, Empty, Skeleton, Tabs, Typography } from "antd";
import { App, Button, Descriptions, Drawer, Skeleton, Tabs, Typography } from "antd";
import { EmptyState } from "@/components/ui/product/empty-state";
import { RefreshCw } from "lucide-react";
import { formatCredits } from "@/constant/credits";
@@ -49,7 +50,7 @@ export function ApiLogDetailDrawer({ logId, onClose, onLogUpdated }: { logId: st
return (
<Drawer title="请求详情" open={Boolean(logId)} onClose={onClose} size="min(920px, 100vw)" destroyOnHidden rootClassName="admin-drawer">
{loading ? <Skeleton active paragraph={{ rows: 12 }} /> : log ? <LogDetail log={log} querying={querying} onQueryProviderTask={queryProviderTask} /> : <Empty description="没有请求详情" />}
{loading ? <Skeleton active paragraph={{ rows: 12 }} /> : log ? <LogDetail log={log} querying={querying} onQueryProviderTask={queryProviderTask} /> : <EmptyState size="compact" title="没有请求详情" />}
</Drawer>
);
}
@@ -125,7 +126,7 @@ function requestKindText(value: ApiCallLog["requestKind"]) {
}
function PayloadPanel({ value, empty }: { value?: string; empty: string }) {
if (!value) return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={empty} />;
if (!value) return <EmptyState size="compact" title={empty} />;
return (
<div className="relative">
<div className="absolute right-3 top-2 z-10">

View File

@@ -1,4 +1,6 @@
import { App, Button, Form, Input, InputNumber, Select, Skeleton, Switch } from "antd";
import { App, Button, Form, Input, InputNumber, Skeleton } from "antd";
import { Select } from "@/components/ui/base/select";
import { Switch } from "@/components/ui/base/switch";
import { AlertTriangle, AtSign, BadgeCheck, KeyRound, MailCheck, RefreshCw, RotateCcw, Save, Send, Server } from "lucide-react";
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { useBlocker } from "react-router";

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { App, Button, Skeleton, Switch } from "antd";
import { App, Button, Skeleton } from "antd";
import { Switch } from "@/components/ui/base/switch";
import { AlertTriangle, Clapperboard, Coins, ListChecks, LockKeyhole, MonitorCog, PlugZap, RadioTower, RefreshCw, ShieldCheck, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";