Files
native/src/components/calendar/EventCard.tsx
T
Linus RathandClaude Opus 4.7 5e99bb36b2 Sync local work: multi-account, calendar/contacts, compose, perf
Contents accumulated in the working tree since the initial commit:

- Multi-account: account-store, per-accountId credential scoping, account
  switcher in sidebar, add-account flow on LoginScreen
- Calendar: recurring event expansion, EventModal/EventDetailSheet, week/
  agenda views, calendar sidebar, participant input, per-day event index
  for O(1) cell lookup (MonthView/WeekView/AgendaView)
- Contacts: contact detail/form/group screens, vCard import/export,
  avatar utils, contact picker sheet, sidebar drawer
- Email: compose screen, thread screen, sender avatar, EmailBodyView with
  CID image resolution and external-content policy
- Infrastructure: navigation types, mailbox tree helper, settings store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 01:21:50 +02:00

104 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { Clock, MapPin, Users } from 'lucide-react-native';
import { format } from 'date-fns';
import type { Calendar, CalendarEvent } from '../../api/types';
import { colors, radius, spacing, typography } from '../../theme/tokens';
import { eventTimeRange, getEventColor } from '../../lib/calendar-utils';
interface EventCardProps {
event: CalendarEvent;
calendars: Calendar[];
onPress?: (event: CalendarEvent) => void;
onLongPress?: (event: CalendarEvent) => void;
}
function participantCount(event: CalendarEvent): number {
return event.participants ? Object.keys(event.participants).length : 0;
}
function formatTimeRange(event: CalendarEvent): string {
const { start, end, allDay } = eventTimeRange(event);
if (allDay) return 'All day';
return `${format(start, 'HH:mm')} – ${format(end, 'HH:mm')}`;
}
export function EventCard({ event, calendars, onPress, onLongPress }: EventCardProps) {
const color = getEventColor(event, calendars);
const time = formatTimeRange(event);
const count = participantCount(event);
return (
<Pressable
style={({ pressed }) => [styles.card, pressed && styles.cardPressed]}
onPress={() => onPress?.(event)}
onLongPress={() => onLongPress?.(event)}
>
<View style={[styles.colorBar, { backgroundColor: color }]} />
<View style={styles.body}>
<View style={styles.headerRow}>
<Text style={styles.title} numberOfLines={1}>
{event.title || 'Untitled'}
</Text>
{event.showWithoutTime && (
<View style={styles.allDayBadge}>
<Text style={styles.allDayText}>All day</Text>
</View>
)}
</View>
{!event.showWithoutTime && (
<View style={styles.detailRow}>
<Clock size={12} color={colors.textMuted} />
<Text style={styles.detailText}>{time}</Text>
</View>
)}
{event.description ? (
<View style={styles.detailRow}>
<MapPin size={12} color={colors.textMuted} />
<Text style={styles.detailText} numberOfLines={1}>
{event.description}
</Text>
</View>
) : null}
{count > 0 && (
<View style={styles.detailRow}>
<Users size={12} color={colors.textMuted} />
<Text style={styles.detailText}>{count} participants</Text>
</View>
)}
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
backgroundColor: colors.card,
borderRadius: radius.lg,
marginBottom: spacing.sm,
overflow: 'hidden',
borderWidth: 1,
borderColor: colors.border,
},
cardPressed: { backgroundColor: colors.surfaceHover },
colorBar: { width: 3 },
body: { flex: 1, padding: spacing.md, gap: 4 },
headerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: spacing.sm,
},
title: { ...typography.bodyMedium, color: colors.text, flex: 1 },
allDayBadge: {
backgroundColor: colors.primaryBg,
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: radius.full,
},
allDayText: { ...typography.small, color: colors.primary },
detailRow: { flexDirection: 'row', alignItems: 'center', gap: 6 },
detailText: { ...typography.caption, color: colors.textMuted, flexShrink: 1 },
});