"添加前端模板和运行代码模块"

This commit is contained in:
Codex
2026-06-01 13:43:09 +08:00
parent 24045274ad
commit 8092c4b1f8
587 changed files with 99761 additions and 0 deletions

View File

@@ -0,0 +1,484 @@
/**
* @fileoverview 颜色面板组件
* @description 展示和管理设计系统中的颜色令牌
*/
import React, { useState, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Search, Filter, Grid, List, Plus, MoreVertical } from 'lucide-react';
import { Card, CardBody, CardHeader } from '../ui/Card';
import { Button, IconButton } from '../ui/Button';
import { ColorToken } from '../../types/design-system';
import { useColors, useDesignSystemStore } from '../../store/design-system-store';
import { clsx } from 'clsx';
// 颜色面板属性接口
interface ColorPaletteProps {
title?: string;
variant?: 'grid' | 'list' | 'swatch';
showControls?: boolean;
interactive?: boolean;
className?: string;
}
// 颜色色板属性接口
interface ColorSwatchProps {
token: ColorToken;
variant?: 'default' | 'compact' | 'large';
showLabel?: boolean;
showValue?: boolean;
interactive?: boolean;
onClick?: () => void;
className?: string;
}
/**
* 颜色色板组件
*/
const ColorSwatch: React.FC<ColorSwatchProps> = ({
token,
variant = 'default',
showLabel = true,
showValue = true,
interactive = true,
onClick,
className
}) => {
const [copied, setCopied] = useState(false);
const { setSelectedToken } = useDesignSystemStore();
// 复制颜色值
const copyColor = async (e: React.MouseEvent) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(token.value);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error('Failed to copy color:', error);
}
};
// 处理点击
const handleClick = () => {
if (interactive) {
setSelectedToken(token);
onClick?.();
}
};
// 尺寸配置
const sizeConfig = {
default: { width: 'w-full', height: 'h-20', padding: 'p-4' },
compact: { width: 'w-full', height: 'h-12', padding: 'p-2' },
large: { width: 'w-full', height: 'h-32', padding: 'p-6' },
};
const config = sizeConfig[variant];
// 显示文本颜色(基于背景亮度)
const getContrastColor = (hexColor: string) => {
// 简单的亮度计算
const r = parseInt(hexColor.slice(1, 3), 16);
const g = parseInt(hexColor.slice(3, 5), 16);
const b = parseInt(hexColor.slice(5, 7), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128 ? '#000000' : '#ffffff';
};
return (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
whileHover={interactive ? { scale: 1.02 } : {}}
whileTap={interactive ? { scale: 0.98 } : {}}
className={clsx(
'color-swatch cursor-pointer group relative overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700 transition-all duration-200',
config.width,
config.height,
{
'hover:shadow-lg': interactive,
'hover:border-primary-300': interactive,
},
className
)}
data-component="ColorSwatch"
data-element="swatch"
data-token-name={token.name}
data-palette={token.palette}
data-scale={token.scale}
style={{ backgroundColor: token.value }}
onClick={handleClick}
>
{/* 颜色背景 */}
<div
className="absolute inset-0"
style={{ backgroundColor: token.value }}
data-element="background"
/>
{/* 覆盖层 */}
<div
className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-200"
data-element="overlay"
/>
{/* 色板内容 */}
<div
className={clsx(
'relative z-10 flex flex-col justify-between h-full',
config.padding
)}
data-element="content"
>
{/* 顶部标签 */}
{showLabel && (
<div className="flex items-center justify-between" data-element="header">
<div className="flex items-center space-x-2" data-element="labels">
<span
className="px-2 py-1 text-xs font-semibold rounded backdrop-blur-sm"
style={{
backgroundColor: 'rgba(0, 0, 0, 0.2)',
color: getContrastColor(token.value)
}}
data-element="name"
>
{token.name}
</span>
{token.deprecated && (
<span className="px-1.5 py-0.5 text-xs bg-warning-500 text-white rounded backdrop-blur-sm">
Deprecated
</span>
)}
</div>
{interactive && (
<IconButton
size="sm"
variant="ghost"
onClick={copyColor}
className="opacity-0 group-hover:opacity-100 transition-opacity"
data-component="IconButton"
data-element="copy-button"
data-action="copy-color"
>
{copied ? (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
className="text-success-400"
>
</motion.div>
) : (
<span
className="text-sm font-mono"
style={{ color: getContrastColor(token.value) }}
>
#
</span>
)}
</IconButton>
)}
</div>
)}
{/* 底部信息 */}
{showValue && (
<div className="space-y-1" data-element="footer">
<div
className="text-xs font-mono backdrop-blur-sm px-2 py-1 rounded"
style={{
backgroundColor: 'rgba(0, 0, 0, 0.2)',
color: getContrastColor(token.value)
}}
data-element="value"
>
{token.value}
</div>
{token.scale && token.palette && (
<div
className="text-xs backdrop-blur-sm px-2 py-1 rounded"
style={{
backgroundColor: 'rgba(0, 0, 0, 0.1)',
color: getContrastColor(token.value)
}}
data-element="metadata"
>
{token.palette} {token.scale}
</div>
)}
</div>
)}
</div>
{/* 选择指示器 */}
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute top-2 right-2 w-3 h-3 bg-primary-500 rounded-full border-2 border-white shadow-sm"
data-element="selected-indicator"
/>
</AnimatePresence>
</motion.div>
);
};
/**
* 颜色面板主组件
*/
const ColorPalette: React.FC<ColorPaletteProps> = ({
title = 'Color Palette',
variant = 'grid',
showControls = true,
interactive = true,
className
}) => {
const [searchQuery, setSearchQuery] = useState('');
const [selectedPalette, setSelectedPalette] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [showFilters, setShowFilters] = useState(false);
const colors = useColors();
const { setSelectedToken, selectedToken } = useDesignSystemStore();
// 过滤和搜索颜色
const filteredColors = useMemo(() => {
return colors.filter(color => {
const matchesSearch = searchQuery === '' ||
color.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
color.value.toLowerCase().includes(searchQuery.toLowerCase()) ||
color.description?.toLowerCase().includes(searchQuery.toLowerCase());
const matchesPalette = selectedPalette === null ||
color.palette === selectedPalette;
return matchesSearch && matchesPalette;
});
}, [colors, searchQuery, selectedPalette]);
// 按调色板分组
const colorsByPalette = useMemo(() => {
const grouped: Record<string, ColorToken[]> = {};
filteredColors.forEach(color => {
const palette = color.palette || 'other';
if (!grouped[palette]) {
grouped[palette] = [];
}
grouped[palette].push(color);
});
return grouped;
}, [filteredColors]);
// 获取所有调色板
const palettes = useMemo(() => {
const paletteSet = new Set(colors.map(color => color.palette || 'other'));
return Array.from(paletteSet);
}, [colors]);
return (
<div
className={clsx('color-palette', className)}
data-component="ColorPalette"
data-element="palette"
data-variant={variant}
data-view-mode={viewMode}
>
{/* 控制面板 */}
{showControls && (
<Card className="mb-6">
<CardBody className="p-4">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
{/* 标题 */}
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
{title}
</h3>
{/* 控制按钮 */}
<div className="flex items-center space-x-3">
{/* 搜索框 */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-neutral-400 w-4 h-4" />
<input
type="text"
placeholder="Search colors..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 pr-4 py-2 border border-neutral-300 dark:border-neutral-600 rounded-md bg-white dark:bg-neutral-800 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
data-component="Input"
data-element="search-input"
data-placeholder="search-colors"
/>
</div>
{/* 过滤器按钮 */}
<IconButton
variant={showFilters ? 'primary' : 'ghost'}
onClick={() => setShowFilters(!showFilters)}
data-component="IconButton"
data-element="filter-button"
data-action="toggle-filters"
>
<Filter className="w-4 h-4" />
</IconButton>
{/* 视图模式切换 */}
<div className="flex border border-neutral-300 dark:border-neutral-600 rounded-md overflow-hidden">
<button
onClick={() => setViewMode('grid')}
className={clsx(
'px-3 py-2 text-sm transition-colors',
viewMode === 'grid'
? 'bg-primary-500 text-white'
: 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
)}
data-component="ToggleButton"
data-element="grid-view-button"
data-action="set-view-grid"
>
<Grid className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('list')}
className={clsx(
'px-3 py-2 text-sm transition-colors',
viewMode === 'list'
? 'bg-primary-500 text-white'
: 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
)}
data-component="ToggleButton"
data-element="list-view-button"
data-action="set-view-list"
>
<List className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* 过滤器面板 */}
<AnimatePresence>
{showFilters && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="mt-4 pt-4 border-t border-neutral-200 dark:border-neutral-800"
data-element="filters"
>
<div className="flex flex-wrap gap-2">
<button
onClick={() => setSelectedPalette(null)}
className={clsx(
'px-3 py-1.5 text-sm rounded-full border transition-colors',
selectedPalette === null
? 'bg-primary-500 text-white border-primary-500'
: 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border-neutral-300 dark:border-neutral-600 hover:bg-neutral-50 dark:hover:bg-neutral-700'
)}
data-component="FilterChip"
data-element="all-palettes-chip"
data-action="select-all-palettes"
>
All Palettes
</button>
{palettes.map(palette => (
<button
key={palette}
onClick={() => setSelectedPalette(palette)}
className={clsx(
'px-3 py-1.5 text-sm rounded-full border transition-colors capitalize',
selectedPalette === palette
? 'bg-primary-500 text-white border-primary-500'
: 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border-neutral-300 dark:border-neutral-600 hover:bg-neutral-50 dark:hover:bg-neutral-700'
)}
data-component="FilterChip"
data-element={`${palette}-palette-chip`}
data-action={`select-${palette}-palette`}
>
{palette}
</button>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</CardBody>
</Card>
)}
{/* 颜色网格/列表 */}
<div className="space-y-8">
{Object.entries(colorsByPalette).map(([palette, paletteColors]) => (
<div key={palette} className="palette-group" data-element="palette-group" data-palette={palette}>
<h4 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-4 capitalize">
{palette} Palette ({paletteColors.length})
</h4>
<div className={clsx(
'grid gap-4',
{
'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8': viewMode === 'grid',
'space-y-2': viewMode === 'list',
}
)}>
<AnimatePresence>
{paletteColors.map((color, index) => (
<motion.div
key={color.name}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
>
<ColorSwatch
token={color}
variant={viewMode === 'list' ? 'compact' : 'default'}
showLabel={viewMode === 'grid'}
showValue={viewMode === 'grid'}
interactive={interactive}
/>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
))}
</div>
{/* 空状态 */}
{filteredColors.length === 0 && (
<div className="text-center py-12" data-element="empty-state">
<div className="w-12 h-12 bg-neutral-100 dark:bg-neutral-800 rounded-full flex items-center justify-center mx-auto mb-4">
<Search className="w-6 h-6 text-neutral-400" />
</div>
<h3 className="text-lg font-medium text-neutral-900 dark:text-neutral-100 mb-2">
No colors found
</h3>
<p className="text-neutral-600 dark:text-neutral-400 mb-4">
Try adjusting your search or filter criteria.
</p>
<Button
variant="outline"
onClick={() => {
setSearchQuery('');
setSelectedPalette(null);
}}
data-component="Button"
data-element="clear-filters-button"
data-action="clear-all-filters"
>
Clear Filters
</Button>
</div>
)}
</div>
);
};
export { ColorPalette, ColorSwatch };
export type { ColorPaletteProps, ColorSwatchProps };
export default ColorPalette;

View File

@@ -0,0 +1,395 @@
/**
* @fileoverview 令牌卡片组件
* @description 展示设计系统令牌的可视化组件
*/
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { Copy, Check, Edit3, Trash2, Tag } from 'lucide-react';
import { Card, CardBody, CardHeader } from '../ui/Card';
import { Button, IconButton } from '../ui/Button';
import { DesignToken, ColorToken, TypographyToken, SpacingToken, ShadowToken, BorderRadiusToken, AnimationToken } from '../../types/design-system';
import { useDesignSystemStore } from '../../store/design-system-store';
import { clsx } from 'clsx';
// 令牌卡片属性接口
interface TokenCardProps {
token: DesignToken;
variant?: 'default' | 'compact' | 'detailed';
interactive?: boolean;
showActions?: boolean;
className?: string;
}
/**
* 令牌卡片主组件
*/
const TokenCard: React.FC<TokenCardProps> = ({
token,
variant = 'default',
interactive = true,
showActions = true,
className
}) => {
const [copied, setCopied] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const { setSelectedToken } = useDesignSystemStore();
// 复制令牌值
const copyToken = async () => {
try {
await navigator.clipboard.writeText(String(token.value));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error('Failed to copy token:', error);
}
};
// 选择令牌
const handleSelect = () => {
if (interactive) {
setSelectedToken(token);
}
};
// 渲染令牌预览
const renderTokenPreview = () => {
switch (token.category) {
case 'color':
return <ColorTokenPreview token={token as ColorToken} />;
case 'typography':
return <TypographyTokenPreview token={token as TypographyToken} />;
case 'spacing':
return <SpacingTokenPreview token={token as SpacingToken} />;
case 'shadow':
return <ShadowTokenPreview token={token as ShadowToken} />;
case 'border-radius':
return <BorderRadiusTokenPreview token={token as BorderRadiusToken} />;
case 'animation':
return <AnimationTokenPreview token={token as AnimationToken} />;
default:
return <DefaultTokenPreview token={token} />;
}
};
// 渲染令牌信息
const renderTokenInfo = () => {
const categoryIcons = {
color: '🎨',
typography: '📝',
spacing: '📏',
shadow: '🌑',
'border-radius': '🔘',
'z-index': '📊',
animation: '⚡',
};
return (
<div className="token-info" data-element="info">
<div className="flex items-center space-x-2 mb-2" data-element="header">
<span className="text-lg" data-element="icon">
{categoryIcons[token.category]}
</span>
<h4 className="font-semibold text-sm text-neutral-900 dark:text-neutral-100" data-element="name">
{token.name}
</h4>
{token.deprecated && (
<span className="px-2 py-0.5 text-xs bg-warning-100 text-warning-800 dark:bg-warning-900/40 dark:text-warning-200 rounded-full">
Deprecated
</span>
)}
</div>
{token.description && (
<p className="text-xs text-neutral-600 dark:text-neutral-400 mb-3" data-element="description">
{token.description}
</p>
)}
{token.tags && token.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mb-3" data-element="tags">
{token.tags.slice(0, 3).map((tag, index) => (
<span
key={index}
className="inline-flex items-center px-2 py-0.5 text-xs bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 rounded"
>
<Tag className="w-3 h-3 mr-1" />
{tag}
</span>
))}
{token.tags.length > 3 && (
<span className="text-xs text-neutral-500">
+{token.tags.length - 3} more
</span>
)}
</div>
)}
</div>
);
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
whileHover={interactive ? { y: -2 } : {}}
className={clsx(
'token-card',
{
'cursor-pointer': interactive,
'opacity-75': token.deprecated,
},
className
)}
data-component="TokenCard"
data-element="card"
data-category={token.category}
data-name={token.name}
onClick={handleSelect}
>
<Card
variant="default"
size="sm"
hover={interactive}
interactive={interactive}
className="h-full"
>
<CardBody className="p-4">
{/* 令牌预览 */}
<div className="token-preview mb-4" data-element="preview">
{renderTokenPreview()}
</div>
{/* 令牌信息 */}
{renderTokenInfo()}
{/* 令牌值和操作 */}
<div className="token-actions flex items-center justify-between mt-4 pt-3 border-t border-neutral-200 dark:border-neutral-800" data-element="actions">
<div className="token-value flex items-center space-x-2" data-element="value">
<code className="text-xs bg-neutral-100 dark:bg-neutral-800 px-2 py-1 rounded font-mono">
{typeof token.value === 'object' ? JSON.stringify(token.value) : token.value}
</code>
</div>
{showActions && (
<div className="token-buttons flex items-center space-x-1" data-element="buttons">
<IconButton
size="sm"
variant="ghost"
onClick={(e) => {
e.stopPropagation();
copyToken();
}}
aria-label="Copy token value"
data-component="IconButton"
data-element="copy-button"
data-action="copy-value"
>
{copied ? (
<Check className="w-3 h-3 text-success-600" />
) : (
<Copy className="w-3 h-3" />
)}
</IconButton>
{interactive && (
<IconButton
size="sm"
variant="ghost"
onClick={(e) => {
e.stopPropagation();
setIsEditing(!isEditing);
}}
aria-label="Edit token"
data-component="IconButton"
data-element="edit-button"
data-action="edit-token"
>
<Edit3 className="w-3 h-3" />
</IconButton>
)}
</div>
)}
</div>
{/* 编辑模式 */}
{isEditing && interactive && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="token-editor mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-800"
data-element="editor"
>
<div className="space-y-2">
<label className="block text-xs font-medium text-neutral-700 dark:text-neutral-300">
Token Value
</label>
<textarea
className="w-full px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded bg-white dark:bg-neutral-800"
rows={2}
value={typeof token.value === 'object' ? JSON.stringify(token.value, null, 2) : String(token.value)}
readOnly
/>
<div className="flex justify-end space-x-2">
<Button
size="sm"
variant="ghost"
onClick={() => setIsEditing(false)}
>
Cancel
</Button>
<Button
size="sm"
variant="primary"
>
Save
</Button>
</div>
</div>
</motion.div>
)}
</CardBody>
</Card>
</motion.div>
);
};
// 颜色令牌预览组件
const ColorTokenPreview: React.FC<{ token: ColorToken }> = ({ token }) => {
return (
<div className="color-token-preview" data-element="color-preview">
<div
className="color-swatch w-full h-16 rounded-lg border border-neutral-200 dark:border-neutral-700"
style={{ backgroundColor: token.value }}
data-element="swatch"
/>
<div className="color-info mt-2 text-center">
<div className="text-xs font-mono" data-element="hex-value">
{token.value}
</div>
{token.scale && (
<div className="text-xs text-neutral-500" data-element="scale">
{token.palette} {token.scale}
</div>
)}
</div>
</div>
);
};
// 字体令牌预览组件
const TypographyTokenPreview: React.FC<{ token: TypographyToken }> = ({ token }) => {
const { fontSize, fontWeight, lineHeight } = token.value;
return (
<div className="typography-token-preview p-3 bg-neutral-50 dark:bg-neutral-800 rounded" data-element="typography-preview">
<div
style={{
fontSize: fontSize || '1rem',
fontWeight: fontWeight || 400,
lineHeight: lineHeight || '1.5',
}}
data-element="text-sample"
>
The quick brown fox
</div>
<div className="mt-2 text-xs text-neutral-600 dark:text-neutral-400 space-y-1" data-element="typography-info">
{fontSize && <div>Size: {fontSize}</div>}
{fontWeight && <div>Weight: {fontWeight}</div>}
{lineHeight && <div>Line Height: {lineHeight}</div>}
</div>
</div>
);
};
// 间距令牌预览组件
const SpacingTokenPreview: React.FC<{ token: SpacingToken }> = ({ token }) => {
return (
<div className="spacing-token-preview" data-element="spacing-preview">
<div className="flex items-center space-x-2">
<div
className="bg-primary-200 dark:bg-primary-800 h-4 rounded"
style={{ width: token.value }}
data-element="spacing-bar"
/>
<span className="text-xs font-mono" data-element="spacing-value">
{token.value}
</span>
</div>
</div>
);
};
// 阴影令牌预览组件
const ShadowTokenPreview: React.FC<{ token: ShadowToken }> = ({ token }) => {
const { offsetX = '0', offsetY = '0', blurRadius = '0', spreadRadius = '0', color = 'transparent' } = token.value;
return (
<div className="shadow-token-preview" data-element="shadow-preview">
<div
className="shadow-demo w-full h-16 bg-white dark:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700"
style={{
boxShadow: `${offsetX} ${offsetY} ${blurRadius} ${spreadRadius} ${color}`,
}}
data-element="shadow-box"
/>
<div className="mt-2 text-xs text-neutral-600 dark:text-neutral-400 space-y-1" data-element="shadow-info">
<div>X: {offsetX}</div>
<div>Y: {offsetY}</div>
<div>Blur: {blurRadius}</div>
<div>Spread: {spreadRadius}</div>
</div>
</div>
);
};
// 圆角令牌预览组件
const BorderRadiusTokenPreview: React.FC<{ token: BorderRadiusToken }> = ({ token }) => {
return (
<div className="border-radius-token-preview" data-element="radius-preview">
<div
className="radius-demo w-full h-16 bg-primary-100 dark:bg-primary-900/20"
style={{ borderRadius: token.value }}
data-element="radius-box"
/>
<div className="mt-2 text-center text-xs font-mono" data-element="radius-value">
{token.value}
</div>
</div>
);
};
// 动画令牌预览组件
const AnimationTokenPreview: React.FC<{ token: AnimationToken }> = ({ token }) => {
const { duration = '300ms', timingFunction = 'ease' } = token.value;
return (
<div className="animation-token-preview" data-element="animation-preview">
<div className="flex items-center space-x-2">
<div className="animation-demo w-8 h-8 bg-primary-500 rounded-full" data-element="animated-circle" />
<div className="text-xs space-y-1" data-element="animation-info">
<div>Duration: {duration}</div>
<div>Easing: {timingFunction}</div>
</div>
</div>
</div>
);
};
// 默认令牌预览组件
const DefaultTokenPreview: React.FC<{ token: DesignToken }> = ({ token }) => {
return (
<div className="default-token-preview p-3 bg-neutral-50 dark:bg-neutral-800 rounded" data-element="default-preview">
<div className="text-center text-sm font-mono" data-element="value">
{typeof token.value === 'object' ? JSON.stringify(token.value) : String(token.value)}
</div>
</div>
);
};
export { TokenCard, ColorTokenPreview, TypographyTokenPreview, SpacingTokenPreview, ShadowTokenPreview, BorderRadiusTokenPreview, AnimationTokenPreview };
export type { TokenCardProps };
export default TokenCard;