Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/components/calendar/EventBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
import { Pressable, StyleSheet, Text, View, useWindowDimensions, type StyleProp, type ViewStyle } from 'react-native';
import { TINTED_BAR_WIDTH, type EventBlockColors } from '../../lib/event-colors';
import { EVENT_TIME_LINE_HEIGHT, EVENT_TITLE_LINE_HEIGHT, timedTitleLines } from '../../lib/calendar-time-grid';

// Events in the week and day grids, shared by the paged WeekView and the
// scrolling TimeGridScrollView. Every event is a solid block of its calendar
Expand Down Expand Up @@ -51,7 +52,9 @@ interface TimedEventBlockProps {
continuesBefore: boolean;
/** The event runs into the next day (square bottom corners). */
continuesAfter: boolean;
/** Position and size in the day column. */
/** Height in the day column; the title wraps over as many lines as it leaves room for. */
height: number;
/** Position and width in the day column. */
style: StyleProp<ViewStyle>;
onPress?: () => void;
/** What a screen reader says: title, date, time (see lib/calendar-a11y). */
Expand All @@ -66,11 +69,13 @@ export function TimedEventBlock({
ringColor,
continuesBefore,
continuesAfter,
height,
style,
onPress,
accessibilityLabel,
}: TimedEventBlockProps) {
const inactive = colors.border !== null;
const { fontScale } = useWindowDimensions();
return (
<Pressable
onPress={onPress}
Expand All @@ -82,6 +87,7 @@ export function TimedEventBlock({
{ backgroundColor: colors.fill, borderColor: ringColor },
colors.bar ? styles.blockWithBar : null,
style,
{ height },
]}
>
{colors.bar ? <View pointerEvents="none" style={[styles.startBar, { backgroundColor: colors.bar }]} /> : null}
Expand All @@ -98,7 +104,7 @@ export function TimedEventBlock({
)}
<Text
style={[styles.blockTitle, { color: colors.text }, inactive && styles.struck]}
numberOfLines={1}
numberOfLines={timedTitleLines(height, timeLabel !== null, fontScale)}
>
{title}
</Text>
Expand Down Expand Up @@ -173,8 +179,8 @@ const styles = StyleSheet.create({
barWithBar: { paddingStart: 4 + TINTED_BAR_WIDTH },
// "Light event colors": the calendar colour along the start edge.
startBar: { position: 'absolute', top: 0, bottom: 0, start: 0, width: TINTED_BAR_WIDTH },
blockTitle: { fontSize: 12, lineHeight: 15, fontWeight: '500' },
blockTime: { fontSize: 10.5, lineHeight: 13, fontWeight: '400', opacity: 0.85 },
blockTitle: { fontSize: 12, lineHeight: EVENT_TITLE_LINE_HEIGHT, fontWeight: '500' },
blockTime: { fontSize: 10.5, lineHeight: EVENT_TIME_LINE_HEIGHT, fontWeight: '400', opacity: 0.85 },
struck: { textDecorationLine: 'line-through' },
bar: {
position: 'absolute',
Expand Down
3 changes: 2 additions & 1 deletion src/components/calendar/TimeGridScrollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,8 @@ const DayColumn = React.memo(function DayColumn({
continuesAfter={continuesAfter}
accessibilityLabel={eventAccessibilityLabel(event, { t, locale, timeFormat, calendars, currentUserEmails })}
onPress={() => onSelectEvent?.(event)}
style={{ top, height, left: `${column * widthPct}%`, width: `${widthPct}%` }}
height={height}
style={{ top, left: `${column * widthPct}%`, width: `${widthPct}%` }}
/>
);
})}
Expand Down
3 changes: 2 additions & 1 deletion src/components/calendar/WeekView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ function WeekViewInner({
continuesAfter={continuesAfter}
accessibilityLabel={eventAccessibilityLabel(event, { t, locale, timeFormat, calendars, currentUserEmails })}
onPress={() => onSelectEvent?.(event)}
style={{ top, height, left: `${leftPct}%`, width: `${widthPct}%` }}
height={height}
style={{ top, left: `${leftPct}%`, width: `${widthPct}%` }}
/>
);
},
Expand Down
27 changes: 27 additions & 0 deletions src/lib/__tests__/calendar-time-grid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
hourAtOffset,
multiDayCount,
timeGridFocusColumn,
timedTitleLines,
windowDays,
} from '../calendar-time-grid';

Expand Down Expand Up @@ -121,6 +122,32 @@ describe('hourAtOffset', () => {
});
});

describe('timedTitleLines', () => {
// The grids draw 48 px an hour and give a block its length less a pixel.
const block = (minutes: number) => Math.max(20, (minutes / 60) * 48 - 1);

it('keeps short events to one line', () => {
expect(timedTitleLines(block(15), false)).toBe(1);
expect(timedTitleLines(block(30), false)).toBe(1);
expect(timedTitleLines(block(45), true)).toBe(1);
});

it('wraps the title of longer events over the room above the time line (#126)', () => {
expect(timedTitleLines(block(60), true)).toBe(2);
expect(timedTitleLines(block(90), true)).toBe(3);
expect(timedTitleLines(block(120), true)).toBe(5);
});

it('counts the room by the larger lines of a bigger system text size', () => {
expect(timedTitleLines(block(60), true, 1.3)).toBe(1);
expect(timedTitleLines(block(120), true, 1.3)).toBe(3);
});

it('never gives less than one line', () => {
expect(timedTitleLines(5, true, 2)).toBe(1);
});
});

describe('multiDayCount', () => {
it('shows three days on a phone, five on a medium window and seven on a wide one', () => {
expect(multiDayCount(411)).toBe(3);
Expand Down
16 changes: 16 additions & 0 deletions src/lib/calendar-time-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ export function shownAllDayRows(rows: number, expanded: boolean): number {
return expanded ? rows : Math.min(rows, ALL_DAY_COLLAPSED_ROWS);
}

/** Line heights of a timed event block's title and time, at the default text size. */
export const EVENT_TITLE_LINE_HEIGHT = 15;
export const EVENT_TIME_LINE_HEIGHT = 13;
/** A timed block's border and padding, top and bottom together. */
const EVENT_BLOCK_INSET = 4;

/**
* Lines of title a timed event block `height` tall has room for: the title
* wraps over what is left once the time line under it is placed, and never
* gets fewer than one. The line heights grow with the system text size.
*/
export function timedTitleLines(height: number, withTimeLine: boolean, fontScale = 1): number {
const room = height - EVENT_BLOCK_INSET - (withTimeLine ? EVENT_TIME_LINE_HEIGHT * fontScale : 0);
return Math.max(1, Math.floor(room / (EVENT_TITLE_LINE_HEIGHT * fontScale)));
}

/** The hour a long press at `offsetY` in a day column lands on. */
export function hourAtOffset(offsetY: number, hourHeight: number): number {
return Math.max(0, Math.min(23, Math.floor(offsetY / hourHeight)));
Expand Down
Loading