feat: improve activity chart on profile
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
format,
|
||||
formatISO,
|
||||
isSameMonth,
|
||||
isToday,
|
||||
startOfMonth,
|
||||
subMonths,
|
||||
} from 'date-fns';
|
||||
@@ -18,15 +19,26 @@ import {
|
||||
WidgetTitle,
|
||||
} from '../overview/overview-widget';
|
||||
import { Button } from '../ui/button';
|
||||
import { Tooltiper } from '../ui/tooltip';
|
||||
|
||||
type Props = {
|
||||
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 = ({
|
||||
month,
|
||||
data,
|
||||
}: { month: Date; data: Props['data'] }) => (
|
||||
maxCount,
|
||||
}: { month: Date; data: Props['data']; maxCount: number }) => (
|
||||
<div>
|
||||
<div className="mb-2 text-sm">{format(month, 'MMMM yyyy')}</div>
|
||||
<div className="-m-1 grid grid-cols-7 gap-1 p-1">
|
||||
@@ -37,14 +49,42 @@ const MonthCalendar = ({
|
||||
const hit = data.find((item) =>
|
||||
item.date.includes(formatISO(date, { representation: 'date' })),
|
||||
);
|
||||
const opacity = hit ? getOpacityLevel(hit.count, maxCount) : 0;
|
||||
return (
|
||||
<div
|
||||
<Tooltiper
|
||||
key={date.toISOString()}
|
||||
className={cn(
|
||||
'aspect-square w-full rounded',
|
||||
hit ? 'bg-highlight' : 'bg-def-200',
|
||||
)}
|
||||
/>
|
||||
asChild
|
||||
content={
|
||||
<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>
|
||||
@@ -53,6 +93,7 @@ const MonthCalendar = ({
|
||||
|
||||
export const ProfileActivity = ({ data }: Props) => {
|
||||
const [startDate, setStartDate] = useState(startOfMonth(new Date()));
|
||||
const maxCount = Math.max(...data.map((item) => item.count), 0);
|
||||
|
||||
return (
|
||||
<Widget className="w-full">
|
||||
@@ -83,6 +124,7 @@ export const ProfileActivity = ({ data }: Props) => {
|
||||
key={offset}
|
||||
month={subMonths(startDate, offset)}
|
||||
data={data}
|
||||
maxCount={maxCount}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user