feat: 添加 CloudWord 组件

This commit is contained in:
2026-01-27 20:15:35 +08:00
parent 22fbd42158
commit a7846ecb61
@@ -0,0 +1,112 @@
'use client';
import React from 'react';
import { cn } from '@/lib/utils';
interface VisualConfig {
color: 'primary' | 'secondary' | 'accent' | 'gray';
size: string;
border: string;
rotation?: string;
}
interface KeywordData {
id: number;
word: string;
trendScore: number;
description: string;
detailPoints: string[];
visualConfig: VisualConfig;
}
interface CloudWordProps {
data: KeywordData;
}
// 颜色映射
const colorMap: Record<string, string> = {
primary: 'bg-primary',
secondary: 'bg-secondary',
accent: 'bg-accent',
gray: 'bg-gray-100 dark:bg-gray-700',
};
export function CloudWord({ data }: CloudWordProps) {
const { word, visualConfig, description, detailPoints } = data;
const { color, size, border, rotation } = visualConfig;
const colorClass = colorMap[color];
return (
<span
className={cn(
'cloud-word',
'inline-block',
'whitespace-nowrap',
'relative',
'transition-all',
'duration-200',
'cursor-pointer',
border,
'border-black',
colorClass,
'px-6',
'py-3',
'rounded-full',
size,
'font-black',
'shadow-hard',
rotation,
'hover:scale-105',
'hover:z-10'
)}
>
{word}
<WordPopover
title={word}
description={description}
points={detailPoints}
titleColor={color}
/>
</span>
);
}
interface WordPopoverProps {
title: string;
description: string;
points: string[];
titleColor: string;
}
function WordPopover({ title, description, points, titleColor }: WordPopoverProps) {
const titleColorClass = colorMap[titleColor] || 'bg-gray-100';
return (
<div className="absolute bottom-[calc(100%+12px)] left-1/2 -translate-x-1/2 w-48 bg-white dark:bg-gray-800 border-2 border-black shadow-hard-sm opacity-0 pointer-events-none transition-all duration-300 translate-y-2 z-50 text-left group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto">
{/* 标题栏 */}
<div className={cn(
'text-black dark:text-white font-display font-bold p-2 border-b-2 border-black text-sm uppercase',
titleColorClass
)}>
{title}
</div>
{/* 内容区域 */}
<div className="p-3 space-y-2 text-xs font-medium dark:text-gray-100">
<div className="flex gap-2 items-start">
<span></span>
<span>{description}</span>
</div>
{points.map((point, i) => (
<div key={i} className="flex gap-2 items-start">
<span></span>
<span>{point}</span>
</div>
))}
</div>
{/* 箭头 */}
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-t-[8px] border-t-black" />
</div>
);
}