mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
feat: show five or seven days in the multi-day calendar view on wider screens
This commit is contained in:
@@ -134,6 +134,7 @@
|
||||
"update_error": "Aufgabe konnte nicht aktualisiert werden"
|
||||
},
|
||||
"views": {
|
||||
"n_days": "{count} Tage",
|
||||
"schedule": "Terminübersicht",
|
||||
"three_days": "3 Tage"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user