Spaces:
Running
Running
File size: 3,899 Bytes
bf8b26e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
import React from 'react';
import { classNames } from '~/utils/classNames';
import { Button } from './Button';
import { motion } from 'framer-motion';
// Variant-specific styles
const VARIANT_STYLES = {
default: {
container: 'py-8 p-6',
icon: {
container: 'w-12 h-12 mb-3',
size: 'w-6 h-6',
},
title: 'text-base',
description: 'text-sm mt-1',
actions: 'mt-4',
buttonSize: 'default' as const,
},
compact: {
container: 'py-4 p-4',
icon: {
container: 'w-10 h-10 mb-2',
size: 'w-5 h-5',
},
title: 'text-sm',
description: 'text-xs mt-0.5',
actions: 'mt-3',
buttonSize: 'sm' as const,
},
};
interface EmptyStateProps {
/** Icon class name */
icon?: string;
/** Title text */
title: string;
/** Optional description text */
description?: string;
/** Primary action button label */
actionLabel?: string;
/** Primary action button callback */
onAction?: () => void;
/** Secondary action button label */
secondaryActionLabel?: string;
/** Secondary action button callback */
onSecondaryAction?: () => void;
/** Additional class name */
className?: string;
/** Component size variant */
variant?: 'default' | 'compact';
}
/**
* EmptyState component
*
* A component for displaying empty states with optional actions.
*/
export function EmptyState({
icon = 'i-ph:folder-simple-dashed',
title,
description,
actionLabel,
onAction,
secondaryActionLabel,
onSecondaryAction,
className,
variant = 'default',
}: EmptyStateProps) {
// Get styles based on variant
const styles = VARIANT_STYLES[variant];
// Animation variants for buttons
const buttonAnimation = {
whileHover: { scale: 1.02 },
whileTap: { scale: 0.98 },
};
return (
<div
className={classNames(
'flex flex-col items-center justify-center',
'text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark',
'bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg',
styles.container,
className,
)}
>
{/* Icon */}
<div
className={classNames(
'rounded-full bg-bolt-elements-background-depth-3 dark:bg-bolt-elements-background-depth-4 flex items-center justify-center',
styles.icon.container,
)}
>
<span
className={classNames(
icon,
styles.icon.size,
'text-bolt-elements-textTertiary dark:text-bolt-elements-textTertiary-dark',
)}
/>
</div>
{/* Title */}
<p className={classNames('font-medium', styles.title)}>{title}</p>
{/* Description */}
{description && (
<p
className={classNames(
'text-bolt-elements-textTertiary dark:text-bolt-elements-textTertiary-dark text-center max-w-xs',
styles.description,
)}
>
{description}
</p>
)}
{/* Action buttons */}
{(actionLabel || secondaryActionLabel) && (
<div className={classNames('flex items-center gap-2', styles.actions)}>
{actionLabel && onAction && (
<motion.div {...buttonAnimation}>
<Button
onClick={onAction}
variant="default"
size={styles.buttonSize}
className="bg-purple-500 hover:bg-purple-600 text-white"
>
{actionLabel}
</Button>
</motion.div>
)}
{secondaryActionLabel && onSecondaryAction && (
<motion.div {...buttonAnimation}>
<Button onClick={onSecondaryAction} variant="outline" size={styles.buttonSize}>
{secondaryActionLabel}
</Button>
</motion.div>
)}
</div>
)}
</div>
);
}
|