73 lines
1.7 KiB
TypeScript
73 lines
1.7 KiB
TypeScript
import { Avatar, Paper, Stack, Text } from "@mantine/core";
|
|
import { type FC, type ReactNode, useState } from "react";
|
|
import classes from "./EntryCard.module.css";
|
|
|
|
type EntryCardProps = {
|
|
title: string;
|
|
metadata: string;
|
|
details?: ReactNode;
|
|
actions?: ReactNode;
|
|
initial: string;
|
|
icon?: ReactNode;
|
|
color?: string;
|
|
imageSrc?: string | null;
|
|
value: string;
|
|
valueLabel: string;
|
|
tone: "positive" | "negative" | "neutral";
|
|
};
|
|
|
|
const EntryCard: FC<EntryCardProps> = (props) => {
|
|
const [imageFailed, setImageFailed] = useState(false);
|
|
return (
|
|
<Paper
|
|
className={classes.row}
|
|
withBorder
|
|
style={
|
|
props.color
|
|
? {
|
|
background: `color-mix(in srgb, var(--mantine-color-${props.color}-0) 45%, white)`,
|
|
}
|
|
: undefined
|
|
}
|
|
>
|
|
<Avatar
|
|
alt=""
|
|
aria-hidden="true"
|
|
radius="md"
|
|
size={40}
|
|
src={imageFailed ? null : props.imageSrc}
|
|
onError={() => setImageFailed(true)}
|
|
>
|
|
{props.icon ?? props.initial}
|
|
</Avatar>
|
|
<Stack className={classes.identity} gap={2}>
|
|
<Text fw={700} lineClamp={1}>
|
|
{props.title}
|
|
</Text>
|
|
<Text c="dimmed" size="sm">
|
|
{props.metadata}
|
|
</Text>
|
|
{props.details}
|
|
</Stack>
|
|
<Stack className={classes.value} gap={2}>
|
|
<Text className={classes[props.tone]} fw={700}>
|
|
{props.value}
|
|
</Text>
|
|
<div className={props.actions ? classes.valueFooter : undefined}>
|
|
<Text
|
|
className={`${classes[props.tone]} ${props.actions ? classes.valueLabel : ""}`}
|
|
size="sm"
|
|
>
|
|
{props.valueLabel}
|
|
</Text>
|
|
{props.actions && (
|
|
<div className={classes.actions}>{props.actions}</div>
|
|
)}
|
|
</div>
|
|
</Stack>
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
export { EntryCard };
|