Files
umami/src/components/charts/ChartAnnotationMarkers.tsx
T

118 lines
4.0 KiB
TypeScript

import { IconLabel } from '@/components/common/IconLabel';
import { useLocale, useMessages } from '@/components/hooks';
import { Maximize } from '@/components/icons';
import { getThemeColors } from '@/lib/colors';
import { formatDate } from '@/lib/date';
import { Button, Column, Text, Tooltip, TooltipTrigger, useTheme } from '@umami/react-zen';
import { isSameDay } from 'date-fns';
import { useMemo } from 'react';
import styles from './ChartAnnotationMarkers.module.css';
const MAX_TOOLTIP_ANNOTATIONS = 3;
export interface ChartAnnotation {
id: string;
date: Date;
markerDate?: Date;
label: string;
allDay?: boolean;
isClickable?: boolean;
isGroupClickable?: boolean;
}
export interface AnnotationMarker {
annotations: ChartAnnotation[];
x: number;
y: number;
}
export function formatAnnotationDate(annotation: ChartAnnotation, locale: string) {
return formatDate(annotation.date, annotation.allDay === false ? 'PP p' : 'PP', locale);
}
export function ChartAnnotationMarkers({
markers,
onClick,
onMoreClick,
}: {
markers: AnnotationMarker[];
onClick?: (annotations: ChartAnnotation[]) => void;
onMoreClick?: (annotations: ChartAnnotation[]) => void;
}) {
const { theme } = useTheme();
const { locale } = useLocale();
const { t, labels } = useMessages();
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
return (
<>
{markers.map(({ annotations, x, y }) => {
const [first] = annotations;
const hasOneDate = annotations.every(annotation => isSameDay(annotation.date, first.date));
const isClickable =
!!onClick &&
(hasOneDate
? first.isClickable !== false
: annotations.every(annotation => annotation.isGroupClickable === true));
const visibleAnnotations = annotations.slice(0, MAX_TOOLTIP_ANNOTATIONS);
return (
<TooltipTrigger key={first.id} delay={0}>
<button
type="button"
className={styles.marker}
style={{
left: x,
top: y,
cursor: isClickable ? 'pointer' : 'default',
backgroundColor: colors.chart.views.hoverBorderColor,
}}
aria-label={annotations.map(({ label }) => label).join(', ')}
onClick={isClickable ? () => onClick?.(annotations) : undefined}
>
{annotations.length > 1 && <span className={styles.count}>{annotations.length}</span>}
</button>
<Tooltip placement="top">
<Column
gap="3"
padding="1"
borderRadius="md"
className={styles.note}
style={{ backgroundColor: 'rgba(0,0,0,0.2)', color: 'white' }}
>
{visibleAnnotations.map(annotation => (
<Column key={annotation.id} gap="1">
<Text size="sm" weight="bold">
{formatAnnotationDate(annotation, locale)}
</Text>
<Text
size="sm"
className={annotations.length === 1 ? styles.noteFull : styles.noteText}
>
{annotation.label}
</Text>
</Column>
))}
{annotations.length > 1 &&
(onMoreClick ? (
<Button
variant="quiet"
className={styles.moreButton}
onPress={() => onMoreClick(annotations)}
>
<IconLabel icon={<Maximize />}>{t(labels.view)}</IconLabel>
</Button>
) : (
<IconLabel icon={<Maximize />} style={{ opacity: 0.8 }}>
{t(labels.view)}
</IconLabel>
))}
</Column>
</Tooltip>
</TooltipTrigger>
);
})}
</>
);
}