feat(ui): CTAButton 可复用组件 + P5 全站 CTA 签名切换(17 页面 27 处)
feat(ui): 抽离 CTAButton 为 src/components/ui/cta-button.tsx - 4 variant 覆盖全站 CTA 场景: * primary:品牌红填充 pill(hero 主行动 / 底部大 CTA) * secondary:描边墨色 pill(浅色 section 次行动) * dark:描边浅色 pill(深色区块 bg-dark-bg 内次行动,如 brand/team 底部 CTA) * inline:透明 + 小箭头(卡片内次行动) - 品牌箭头签名化:内置 ArrowRight + hover translate-x 微交互 - 200-300ms transition,对齐项目动效四原则 - data-testid 透传避免破坏既有测试 style(cta): P5 全站 CTA 签名切换(17 页面 27 处) - home V15 / news×3 / products×4(含 erp)/ cases / services×2 / solutions×2 - brand / methodology / team(hero 2 + L4 2 = 4 处,含 dark variant) - error / not-found(保留 onClick 功能按钮与卡片式导航 StaticLink) - 清理:StaticLink / Button / ArrowRight / ChevronRight unused import test(marketing): services/solutions lucide-react mock 补 ArrowRight - CTAButton 内部用 ArrowRight,原 mock 漏导导致渲染崩(4 测试失败已修) - 对齐 home-content-v15.test.tsx 既有 mock 范式
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/**
|
||||
* CTAButton — 全站统一的「行动召唤」组件
|
||||
*
|
||||
* 设计意图(见 .impeccable.md / Phase B P5):
|
||||
* - Pill 化(rounded-full),与项目 Button 组件 size 体系一致
|
||||
* - 品牌箭头签名化:内置 ArrowRight,hover 时 translate-x 形成微交互
|
||||
* - 四变体覆盖全站 CTA 场景,避免"全是大红 pill"的 UI 噪音:
|
||||
* - primary:品牌红填充 pill + 大箭头(hero 主行动 / 底部大 CTA)
|
||||
* - secondary:描边墨色 pill + 大箭头(浅色 section 次行动 / 跳转按钮)
|
||||
* - dark:描边浅色 pill + 大箭头(深色区块(bg-dark-bg)内的次行动,如 brand/team 底部 CTA)
|
||||
* - inline:透明 + 小箭头 + 文字色(卡片内"了解更多"轻量次行动)
|
||||
*
|
||||
* dark 变体于 2026-08-31 P5 批次 4 新增:brand/team 底部深色 CTA 区块的次行动此前用
|
||||
* text-dark-text-primary + border-dark-border 手写描边,现统一进组件,避免深色背景上误用
|
||||
* secondary(text-ink)导致不可见。
|
||||
* - 200-300ms transition(动效四原则区间内)
|
||||
* - 主行动(primary)触达品牌红 #C41E3A 单电压,遵循"每页 ≥ 3 处品牌红触达点,面积 ≤ 10%"约束
|
||||
*
|
||||
* 抽离自 src/app/(marketing)/home-content-v15.tsx (2026-08-31),原 primary/secondary 行为完全保留;
|
||||
* 新增 inline 变体支持次行动场景;data-testid 透传避免破坏既有测试快照。
|
||||
*/
|
||||
export type CTAButtonVariant = 'primary' | 'secondary' | 'dark' | 'inline';
|
||||
|
||||
export interface CTAButtonProps {
|
||||
children: React.ReactNode;
|
||||
href: string;
|
||||
variant?: CTAButtonVariant;
|
||||
className?: string;
|
||||
dataTestId?: string;
|
||||
/** 内部 span 的额外类名(用于覆盖默认间距/字号等极端定制场景) */
|
||||
innerClassName?: string;
|
||||
}
|
||||
|
||||
const VARIANT_STYLES: Record<CTAButtonVariant, string> = {
|
||||
primary:
|
||||
'bg-brand text-white hover:bg-brand-hover px-10 py-4 font-semibold text-base',
|
||||
secondary:
|
||||
'text-ink border border-ink/20 hover:border-ink/40 hover:bg-ink/5 px-10 py-4 font-semibold text-base',
|
||||
dark:
|
||||
'text-dark-text-primary border border-dark-border hover:border-dark-text-muted hover:bg-white/5 px-10 py-4 font-semibold text-base',
|
||||
inline:
|
||||
'text-text-primary hover:text-brand px-0 py-0 font-medium text-sm bg-transparent border-0',
|
||||
};
|
||||
|
||||
const ARROW_STYLES: Record<CTAButtonVariant, string> = {
|
||||
primary: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||
secondary: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||
dark: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||
inline: 'w-4 h-4 transition-transform duration-300 group-hover:translate-x-1',
|
||||
};
|
||||
|
||||
export function CTAButton({
|
||||
children,
|
||||
href,
|
||||
variant = 'primary',
|
||||
className,
|
||||
dataTestId,
|
||||
innerClassName,
|
||||
}: CTAButtonProps) {
|
||||
const isPill = variant !== 'inline';
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
data-testid={dataTestId}
|
||||
className={cn(
|
||||
'group inline-flex items-center gap-3 transition-all duration-200',
|
||||
isPill ? 'rounded-full' : '',
|
||||
VARIANT_STYLES[variant],
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className={cn(innerClassName)}>{children}</span>
|
||||
<ArrowRight className={ARROW_STYLES[variant]} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default CTAButton;
|
||||
Reference in New Issue
Block a user