feat: 添加 CloudWord 组件
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user