feat: improve activity chart on profile

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-01-22 20:54:08 +01:00
parent b39d076b32
commit 32ea28b2f6

View File

@@ -7,6 +7,7 @@ import {
format, format,
formatISO, formatISO,
isSameMonth, isSameMonth,
isToday,
startOfMonth, startOfMonth,
subMonths, subMonths,
} from 'date-fns'; } from 'date-fns';
@@ -18,15 +19,26 @@ import {
WidgetTitle, WidgetTitle,
} from '../overview/overview-widget'; } from '../overview/overview-widget';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { Tooltiper } from '../ui/tooltip';
type Props = { type Props = {
data: { count: number; date: string }[]; data: { count: number; date: string }[];
}; };
function getOpacityLevel(count: number, maxCount: number): number {
if (count === 0 || maxCount === 0) return 0;
const ratio = count / maxCount;
if (ratio <= 0.25) return 0.25;
if (ratio <= 0.5) return 0.5;
if (ratio <= 0.75) return 0.75;
return 1;
}
const MonthCalendar = ({ const MonthCalendar = ({
month, month,
data, data,
}: { month: Date; data: Props['data'] }) => ( maxCount,
}: { month: Date; data: Props['data']; maxCount: number }) => (
<div> <div>
<div className="mb-2 text-sm">{format(month, 'MMMM yyyy')}</div> <div className="mb-2 text-sm">{format(month, 'MMMM yyyy')}</div>
<div className="-m-1 grid grid-cols-7 gap-1 p-1"> <div className="-m-1 grid grid-cols-7 gap-1 p-1">
@@ -37,14 +49,42 @@ const MonthCalendar = ({
const hit = data.find((item) => const hit = data.find((item) =>
item.date.includes(formatISO(date, { representation: 'date' })), item.date.includes(formatISO(date, { representation: 'date' })),
); );
const opacity = hit ? getOpacityLevel(hit.count, maxCount) : 0;
return ( return (
<div <Tooltiper
key={date.toISOString()} key={date.toISOString()}
className={cn( asChild
'aspect-square w-full rounded', content={
hit ? 'bg-highlight' : 'bg-def-200', <div className="text-sm col gap-1">
)} <div className="font-medium">{format(date, 'EEEE, MMM d')}</div>
/> {hit ? (
<div className="text-muted-foreground">
{hit.count} {hit.count === 1 ? 'event' : 'events'}
</div>
) : (
<div className="text-muted-foreground">No activity</div>
)}
</div>
}
>
<div
className={cn(
'aspect-square w-full rounded cursor-default group hover:ring-1 hover:ring-foreground overflow-hidden',
)}
>
<div
className={cn(
'size-full group-hover:shadow-[inset_0_0_0_2px_var(--background)] rounded',
isToday(date)
? 'bg-highlight'
: hit
? 'bg-foreground'
: 'bg-def-200',
)}
style={hit && !isToday(date) ? { opacity } : undefined}
/>
</div>
</Tooltiper>
); );
})} })}
</div> </div>
@@ -53,6 +93,7 @@ const MonthCalendar = ({
export const ProfileActivity = ({ data }: Props) => { export const ProfileActivity = ({ data }: Props) => {
const [startDate, setStartDate] = useState(startOfMonth(new Date())); const [startDate, setStartDate] = useState(startOfMonth(new Date()));
const maxCount = Math.max(...data.map((item) => item.count), 0);
return ( return (
<Widget className="w-full"> <Widget className="w-full">
@@ -83,6 +124,7 @@ export const ProfileActivity = ({ data }: Props) => {
key={offset} key={offset}
month={subMonths(startDate, offset)} month={subMonths(startDate, offset)}
data={data} data={data}
maxCount={maxCount}
/> />
))} ))}
</div> </div>