feat: show five or seven days in the multi-day calendar view on wider screens

This commit is contained in:
Linus Rath
2026-10-15 02:05:31 +02:00
parent 295e292e47
commit 2288d28e46
6 changed files with 57 additions and 14 deletions
+1
View File
@@ -134,6 +134,7 @@
"update_error": "Aufgabe konnte nicht aktualisiert werden"
},
"views": {
"n_days": "{count} Tage",
"schedule": "Terminübersicht",
"three_days": "3 Tage"
}
+1
View File
@@ -137,6 +137,7 @@
"update_error": "Failed to update task"
},
"views": {
"n_days": "{count} days",
"schedule": "Schedule",
"three_days": "3 days"
}
@@ -61,6 +61,8 @@ type TimeFormat = '12h' | '24h';
interface TimeGridScrollViewProps {
mode: TimeGridMode;
/** Days of the multi-day mode (`multiDayCount`). */
multiDays?: number;
/** The day the user navigated to; its week (week view) or itself (day view) is scrolled into view. */
focus: CalendarFocus;
/** The loaded window: one column per day. */
@@ -96,6 +98,7 @@ interface TimeGridScrollViewProps {
*/
function TimeGridScrollViewInner({
mode,
multiDays = 3,
focus,
window,
selectedDate,
@@ -120,7 +123,7 @@ function TimeGridScrollViewInner({
[eventsByDay, events],
);
const perScreen = columnsPerScreen(mode);
const perScreen = columnsPerScreen(mode, multiDays);
const { width: screenWidth } = useWindowDimensions();
const [rootWidth, setRootWidth] = React.useState(screenWidth);
const listWidth = Math.max(perScreen, rootWidth - GUTTER_WIDTH);
@@ -8,8 +8,10 @@ import {
} from '../calendar-scroll-window';
import {
buildAllDaySegments,
columnsPerScreen,
headerColumnRange,
hourAtOffset,
multiDayCount,
timeGridFocusColumn,
windowDays,
} from '../calendar-time-grid';
@@ -118,3 +120,18 @@ describe('hourAtOffset', () => {
expect(hourAtOffset(-4, 48)).toBe(0);
});
});
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);
expect(multiDayCount(800)).toBe(5);
expect(multiDayCount(1280)).toBe(7);
});
it('only changes the multi-day view', () => {
expect(columnsPerScreen('3day', 7)).toBe(7);
expect(columnsPerScreen('week', 5)).toBe(7);
expect(columnsPerScreen('day', 7)).toBe(1);
expect(columnsPerScreen('3day')).toBe(3);
});
});
+11 -2
View File
@@ -18,9 +18,18 @@ import type { DayRange, WeekStartsOn } from './calendar-scroll-window';
export type TimeGridMode = 'week' | '3day' | 'day';
/**
* Days the multi-day view ("3 days" on a phone) shows for a window `width`
* wide: three on a phone, five on a medium window, a full seven-day strip
* on a wide one, so the extra width buys days rather than wider columns.
*/
export function multiDayCount(width: number): number {
return width >= 840 ? 7 : width >= 600 ? 5 : 3;
}
/** Day columns on one screen. */
export function columnsPerScreen(mode: TimeGridMode): number {
return mode === 'week' ? 7 : mode === '3day' ? 3 : 1;
export function columnsPerScreen(mode: TimeGridMode, multiDays = 3): number {
return mode === 'week' ? 7 : mode === '3day' ? multiDays : 1;
}
/** Every day of the window, in order. */
+23 -11
View File
@@ -8,8 +8,10 @@ import {
StyleSheet,
Pressable,
ActivityIndicator,
useWindowDimensions,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { multiDayCount } from '../lib/calendar-time-grid';
import {
Calendar as CalendarIcon,
ChevronDown,
@@ -110,10 +112,10 @@ function windowMode(mode: ViewMode): ScrollViewMode {
return mode === '3day' ? 'day' : mode;
}
// A window without free scrolling: one period, three days for the three-day grid.
function fixedWindowFor(mode: ViewMode, anchor: Date): ScrollWindowState {
// A window without free scrolling: one period, the multi-day grid's days.
function fixedWindowFor(mode: ViewMode, anchor: Date, multiDays: number): ScrollWindowState {
const state = fixedScrollWindowState(windowMode(mode), anchor);
return mode === '3day' ? { ...state, after: 2 } : state;
return mode === '3day' ? { ...state, after: multiDays - 1 } : state;
}
type PendingAction =
| {
@@ -189,6 +191,9 @@ export default function CalendarScreen() {
);
// The sideways-scrolling week and day grids assume a left-to-right strip;
// right-to-left layouts keep them paged.
// Wider windows show more days in the multi-day view.
const { width: windowWidth } = useWindowDimensions();
const multiDays = multiDayCount(windowWidth);
const freeScroll =
calendarFreeScroll
&& ((viewMode !== 'week' && viewMode !== '3day' && viewMode !== 'day') || !I18nManager.isRTL);
@@ -197,8 +202,8 @@ export default function CalendarScreen() {
() =>
freeScroll
? normalizeScrollWindowState(windowState, windowMode(viewMode), parseDayKey(focusKey))
: fixedWindowFor(viewMode, parseDayKey(focusKey)),
[freeScroll, windowState, viewMode, focusKey],
: fixedWindowFor(viewMode, parseDayKey(focusKey), multiDays),
[freeScroll, windowState, viewMode, focusKey, multiDays],
);
React.useEffect(() => {
if (freeScroll && activeWindowState !== windowState) setWindowState(activeWindowState);
@@ -482,21 +487,21 @@ export default function CalendarScreen() {
const base = visibleDate ?? focus.date;
jumpTo(
viewMode === 'week' ? subWeeks(base, 1)
: viewMode === '3day' ? subDays(base, 3)
: viewMode === '3day' ? subDays(base, multiDays)
: viewMode === 'day' ? subDays(base, 1)
: subMonths(base, 1),
);
}, [viewMode, visibleDate, focus.date, jumpTo]);
}, [viewMode, visibleDate, focus.date, jumpTo, multiDays]);
const goNext = React.useCallback(() => {
const base = visibleDate ?? focus.date;
jumpTo(
viewMode === 'week' ? addWeeks(base, 1)
: viewMode === '3day' ? addDays(base, 3)
: viewMode === '3day' ? addDays(base, multiDays)
: viewMode === 'day' ? addDays(base, 1)
: addMonths(base, 1),
);
}, [viewMode, visibleDate, focus.date, jumpTo]);
}, [viewMode, visibleDate, focus.date, jumpTo, multiDays]);
const goToday = React.useCallback(() => {
jumpTo(displayNow());
@@ -520,11 +525,17 @@ export default function CalendarScreen() {
() => [
{ key: 'agenda', label: t('calendar.views.schedule', 'Schedule'), Icon: Rows2 },
{ key: 'day', label: t('calendar.views.day', 'Day'), Icon: Square },
{ key: '3day', label: t('calendar.views.three_days', '3 days'), Icon: Columns3 },
{
key: '3day',
label: multiDays === 3
? t('calendar.views.three_days', '3 days')
: t('calendar.views.n_days', '{count} days', { count: multiDays }),
Icon: Columns3,
},
{ key: 'week', label: t('calendar.views.week', 'Week'), Icon: Columns4 },
{ key: 'month', label: t('calendar.views.month', 'Month'), Icon: Grid3x3 },
],
[t],
[t, multiDays],
);
// The month that drops down under the title; the title follows the month
@@ -1094,6 +1105,7 @@ export default function CalendarScreen() {
<TimeGridScrollView
key={windowKey}
mode={viewMode}
multiDays={multiDays}
focus={focus}
window={scrollWindow}
onExtendStart={freeScroll && scrollWindow.canExtendStart ? extendWindowStart : undefined}