mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
fix(frontend): restore button border and color semantics
This commit is contained in:
@@ -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` 噪音)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -57,6 +57,79 @@ function mapSize(size: HeroButtonSize, isIconOnly: boolean): "default" | "sm" |
|
||||
return "default";
|
||||
}
|
||||
|
||||
const solidColorClassMap: Partial<Record<HeroButtonColor, string>> = {
|
||||
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<HeroButtonColor, string> = {
|
||||
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<HeroButtonColor, string> = {
|
||||
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<HeroButtonColor, string> = {
|
||||
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<HeroButtonColor, string> = {
|
||||
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<HTMLButtonElement>) => {
|
||||
onClick?.(event);
|
||||
@@ -94,7 +169,7 @@ export const Button = React.forwardRef<
|
||||
|
||||
return (
|
||||
<BaseButton
|
||||
className={cn(isIconOnly ? "p-0" : "", className)}
|
||||
className={cn(isIconOnly ? "p-0" : "", resolvedColorClass, resolvedShadowClass, className)}
|
||||
disabled={resolvedDisabled}
|
||||
ref={ref}
|
||||
size={resolvedSize}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "./tailwind-theme.pcss";
|
||||
|
||||
:root {
|
||||
--safe-area-top: env(safe-area-inset-top, 0px);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-content1: var(--content1);
|
||||
--color-divider: var(--divider);
|
||||
|
||||
--color-default-50: var(--default-50);
|
||||
--color-default-100: var(--default-100);
|
||||
--color-default-200: var(--default-200);
|
||||
--color-default-300: var(--default-300);
|
||||
--color-default-400: var(--default-400);
|
||||
--color-default-500: var(--default-500);
|
||||
--color-default-600: var(--default-600);
|
||||
--color-default-700: var(--default-700);
|
||||
--color-default-800: var(--default-800);
|
||||
--color-default-900: var(--default-900);
|
||||
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary-50: var(--primary-50);
|
||||
--color-primary-100: var(--primary-100);
|
||||
--color-primary-200: var(--primary-200);
|
||||
--color-primary-300: var(--primary-300);
|
||||
--color-primary-400: var(--primary-400);
|
||||
--color-primary-500: var(--primary-500);
|
||||
--color-primary-600: var(--primary-600);
|
||||
--color-primary-700: var(--primary-700);
|
||||
--color-primary-800: var(--primary-800);
|
||||
--color-primary-900: var(--primary-900);
|
||||
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary-50: var(--secondary-50);
|
||||
--color-secondary-100: var(--secondary-100);
|
||||
--color-secondary-200: var(--secondary-200);
|
||||
--color-secondary-300: var(--secondary-300);
|
||||
--color-secondary-400: var(--secondary-400);
|
||||
--color-secondary-500: var(--secondary-500);
|
||||
--color-secondary-600: var(--secondary-600);
|
||||
--color-secondary-700: var(--secondary-700);
|
||||
--color-secondary-800: var(--secondary-800);
|
||||
--color-secondary-900: var(--secondary-900);
|
||||
|
||||
--color-danger: var(--danger);
|
||||
--color-danger-50: var(--danger-50);
|
||||
--color-danger-100: var(--danger-100);
|
||||
--color-danger-200: var(--danger-200);
|
||||
--color-danger-300: var(--danger-300);
|
||||
--color-danger-400: var(--danger-400);
|
||||
--color-danger-500: var(--danger-500);
|
||||
--color-danger-600: var(--danger-600);
|
||||
--color-danger-700: var(--danger-700);
|
||||
--color-danger-800: var(--danger-800);
|
||||
--color-danger-900: var(--danger-900);
|
||||
|
||||
--color-success: var(--success);
|
||||
--color-success-50: var(--success-50);
|
||||
--color-success-100: var(--success-100);
|
||||
--color-success-200: var(--success-200);
|
||||
--color-success-300: var(--success-300);
|
||||
--color-success-400: var(--success-400);
|
||||
--color-success-500: var(--success-500);
|
||||
--color-success-600: var(--success-600);
|
||||
--color-success-700: var(--success-700);
|
||||
--color-success-800: var(--success-800);
|
||||
--color-success-900: var(--success-900);
|
||||
|
||||
--color-warning: var(--warning);
|
||||
--color-warning-50: var(--warning-50);
|
||||
--color-warning-100: var(--warning-100);
|
||||
--color-warning-200: var(--warning-200);
|
||||
--color-warning-300: var(--warning-300);
|
||||
--color-warning-400: var(--warning-400);
|
||||
--color-warning-500: var(--warning-500);
|
||||
--color-warning-600: var(--warning-600);
|
||||
--color-warning-700: var(--warning-700);
|
||||
--color-warning-800: var(--warning-800);
|
||||
--color-warning-900: var(--warning-900);
|
||||
}
|
||||
Reference in New Issue
Block a user