diff --git a/vite-frontend/docs/shadcn-ui-full-migration-2026-02-19.md b/vite-frontend/docs/shadcn-ui-full-migration-2026-02-19.md index c0d5be9..897ed55 100644 --- a/vite-frontend/docs/shadcn-ui-full-migration-2026-02-19.md +++ b/vite-frontend/docs/shadcn-ui-full-migration-2026-02-19.md @@ -47,3 +47,6 @@ - [2026-02-19] 完成 S8:构建验收通过(`npm run build`),`package.json` 已无 `heroui/nextui` 依赖 - [2026-02-19] 验证结果:业务代码中 `@heroui/*` 导入为 `0`,已统一替换为 `@/shadcn-bridge/heroui/*`(22 文件,106 处) - [2026-02-19] 后续修复:`src/components/ui/button.tsx` 与 `src/shadcn-bridge/heroui/button.tsx` 改为 `forwardRef`,消除 `DropdownMenuTrigger asChild` 场景 ref 警告;复构建通过 +- [2026-02-19] 回归修复:定位“按钮边框/颜色丢失”根因是 Tailwind v4 下语义色未生成;新增 `src/styles/tailwind-theme.pcss`(由 `src/styles/globals.css` 引入)承载 `@theme inline` token 映射,恢复 `bg-primary`/`text-foreground`/`border-input` 等语义类输出 +- [2026-02-19] 回归修复:增强 `src/shadcn-bridge/heroui/button.tsx` 的 `solid/light/flat/bordered/shadow` 颜色映射,并修正 `src/components/ui/button.tsx` 的 `outline/ghost` hover 语义类,避免依赖未定义的 `accent` 色 +- [2026-02-19] 复验结果:`npm run build` 通过;编译产物已包含关键语义类;页面级视觉回归(forward/tunnel/node/user)通过(后端未启动时仅保留 `ERR_CONNECTION_REFUSED` 噪音) diff --git a/vite-frontend/src/components/ui/button.tsx b/vite-frontend/src/components/ui/button.tsx index 72fd09f..ba4ffb7 100644 --- a/vite-frontend/src/components/ui/button.tsx +++ b/vite-frontend/src/components/ui/button.tsx @@ -11,9 +11,9 @@ const buttonVariants = cva( variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-danger text-white hover:bg-danger/90", - outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + outline: "border border-input bg-background hover:bg-default-100 hover:text-foreground dark:hover:bg-default-200/40", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", + ghost: "hover:bg-default-100 hover:text-foreground dark:hover:bg-default-200/40", link: "text-primary underline-offset-4 hover:underline", light: "bg-transparent hover:bg-default-100 dark:hover:bg-default-200/40", flat: "bg-default-100 text-foreground hover:bg-default-200 dark:bg-default-100/40", diff --git a/vite-frontend/src/shadcn-bridge/heroui/button.tsx b/vite-frontend/src/shadcn-bridge/heroui/button.tsx index 14d9b86..0d7de47 100644 --- a/vite-frontend/src/shadcn-bridge/heroui/button.tsx +++ b/vite-frontend/src/shadcn-bridge/heroui/button.tsx @@ -57,6 +57,79 @@ function mapSize(size: HeroButtonSize, isIconOnly: boolean): "default" | "sm" | return "default"; } +const solidColorClassMap: Partial> = { + default: + "bg-default-200 text-default-800 hover:bg-default-300 dark:bg-default-100 dark:text-default-800 dark:hover:bg-default-200", + success: "bg-success text-white hover:bg-success-600", + warning: "bg-warning text-warning-900 hover:bg-warning-400", +}; + +const borderedColorClassMap: Record = { + default: + "border-default-300 text-default-700 hover:bg-default-100 dark:border-default-300 dark:text-default-600 dark:hover:bg-default-200/40", + primary: "border-primary text-primary hover:bg-primary-50 dark:border-primary-500/60 dark:text-primary-300 dark:hover:bg-primary-900/20", + secondary: + "border-secondary text-secondary hover:bg-secondary-50 dark:border-secondary-500/60 dark:text-secondary-300 dark:hover:bg-secondary-900/20", + success: "border-success text-success hover:bg-success-50 dark:border-success-500/60 dark:text-success-300 dark:hover:bg-success-900/20", + warning: + "border-warning text-warning-700 hover:bg-warning-50 dark:border-warning-500/60 dark:text-warning-300 dark:hover:bg-warning-900/20", + danger: "border-danger text-danger hover:bg-danger-50 dark:border-danger-500/60 dark:text-danger-300 dark:hover:bg-danger-900/20", +}; + +const lightColorClassMap: Record = { + default: "text-default-700 hover:bg-default-100 dark:text-default-600 dark:hover:bg-default-200/40", + primary: "text-primary hover:bg-primary-100/70 dark:text-primary-300 dark:hover:bg-primary-900/30", + secondary: "text-secondary hover:bg-secondary-100/70 dark:text-secondary-300 dark:hover:bg-secondary-900/30", + success: "text-success hover:bg-success-100/70 dark:text-success-300 dark:hover:bg-success-900/30", + warning: "text-warning-700 hover:bg-warning-100/70 dark:text-warning-300 dark:hover:bg-warning-900/30", + danger: "text-danger hover:bg-danger-100/70 dark:text-danger-300 dark:hover:bg-danger-900/30", +}; + +const flatColorClassMap: Record = { + default: + "bg-default-100 text-default-700 hover:bg-default-200 dark:bg-default-100/45 dark:text-default-700 dark:hover:bg-default-200/60", + primary: "bg-primary-100 text-primary-700 hover:bg-primary-200 dark:bg-primary-900/30 dark:text-primary-300 dark:hover:bg-primary-900/45", + secondary: + "bg-secondary-100 text-secondary-700 hover:bg-secondary-200 dark:bg-secondary-900/30 dark:text-secondary-300 dark:hover:bg-secondary-900/45", + success: "bg-success-100 text-success-700 hover:bg-success-200 dark:bg-success-900/30 dark:text-success-300 dark:hover:bg-success-900/45", + warning: "bg-warning-100 text-warning-700 hover:bg-warning-200 dark:bg-warning-900/30 dark:text-warning-300 dark:hover:bg-warning-900/45", + danger: "bg-danger-100 text-danger-700 hover:bg-danger-200 dark:bg-danger-900/30 dark:text-danger-300 dark:hover:bg-danger-900/45", +}; + +const shadowColorClassMap: Record = { + default: "shadow-md shadow-default-400/40", + primary: "shadow-md shadow-primary-500/35", + secondary: "shadow-md shadow-secondary-500/35", + success: "shadow-md shadow-success-500/35", + warning: "shadow-md shadow-warning-500/40", + danger: "shadow-md shadow-danger-500/35", +}; + +function mapColorClass(color: HeroButtonColor, variant: HeroButtonVariant): string { + if (variant === "bordered") { + return borderedColorClassMap[color]; + } + if (variant === "light") { + return lightColorClassMap[color]; + } + if (variant === "flat") { + return flatColorClassMap[color]; + } + if (variant === "solid" || variant === "shadow") { + return solidColorClassMap[color] ?? ""; + } + + return ""; +} + +function mapShadowClass(color: HeroButtonColor, variant: HeroButtonVariant): string { + if (variant !== "shadow") { + return ""; + } + + return shadowColorClassMap[color]; +} + export const Button = React.forwardRef< HTMLButtonElement, ButtonProps & { @@ -86,6 +159,8 @@ export const Button = React.forwardRef< const resolvedVariant = mapVariant(color, variant); const resolvedSize = mapSize(size, isIconOnly); const resolvedDisabled = Boolean(disabled || isDisabled || isLoading); + const resolvedColorClass = mapColorClass(color, variant); + const resolvedShadowClass = mapShadowClass(color, variant); const handleClick = (event: React.MouseEvent) => { onClick?.(event); @@ -94,7 +169,7 @@ export const Button = React.forwardRef< return (