import { Box, Button, Typography } from '@mui/joy'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
/**
* The single empty/error surface for the app.
*
* variant drives the tone, the icon tile color and the a11y role:
* - 'empty' nothing exists yet. Teach the feature, offer the way in.
* - 'no-results' something exists, the current search/filter hides it.
* - 'error' we failed to load. Say what happened, offer a retry.
*
* Actions are objects instead of nodes so every call site gets the same
* button vocabulary (solid primary lead, plain neutral follow).
*/
const TONES = {
empty: {
tileBg: 'primary.softHoverBg',
halo: 'primary.softBg',
iconColor: 'primary.softColor',
role: 'status',
},
'no-results': {
tileBg: 'neutral.softHoverBg',
halo: 'neutral.softBg',
iconColor: 'neutral.softColor',
role: 'status',
},
error: {
tileBg: 'danger.softHoverBg',
halo: 'danger.softBg',
iconColor: 'danger.softColor',
role: 'alert',
},
}
const SIZES = {
sm: {
tile: 48,
icon: '1.375rem',
halo: 6,
py: 5,
title: 'title-sm',
titleSize: '1rem',
},
md: {
tile: 68,
icon: '1.875rem',
halo: 10,
py: 8,
title: 'title-md',
titleSize: '1.25rem',
},
}
const ActionButton = ({ action, ...buttonProps }) => {
const { label, onClick, to, ...rest } = action
return (
)
}
ActionButton.propTypes = {
action: PropTypes.object.isRequired,
}
const EmptyState = ({
description,
fullHeight = false,
icon,
primaryAction,
secondaryAction,
size = 'md',
sx,
title,
variant = 'empty',
...rest
}) => {
const tone = TONES[variant] || TONES.empty
const dimensions = SIZES[size] || SIZES.md
const buttonSize = size === 'sm' ? 'sm' : 'md'
return (
{icon && (
svg': { fontSize: dimensions.icon },
}}
>
{icon}
)}
{title}
{description && (
{description}
)}
{(primaryAction || secondaryAction) && (
{primaryAction && (
)}
{secondaryAction && (
)}
)}
)
}
EmptyState.propTypes = {
variant: PropTypes.oneOf(['empty', 'no-results', 'error']),
icon: PropTypes.node,
title: PropTypes.node.isRequired,
description: PropTypes.node,
primaryAction: PropTypes.shape({
label: PropTypes.node.isRequired,
onClick: PropTypes.func,
to: PropTypes.string,
startDecorator: PropTypes.node,
}),
secondaryAction: PropTypes.shape({
label: PropTypes.node.isRequired,
onClick: PropTypes.func,
to: PropTypes.string,
startDecorator: PropTypes.node,
}),
size: PropTypes.oneOf(['sm', 'md']),
fullHeight: PropTypes.bool,
sx: PropTypes.object,
}
export default EmptyState