feat(calendar): add context menu to CalendarHeader with display settings

Add right-click context menu to the calendar header for customizing:
- Compact view toggle
- Weekday format (full/short/hidden)
- Date visibility toggle
- Month display toggle (e.g., "13.12.")

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-14 23:25:29 +01:00
parent 78fb495ba2
commit 1f7b93af21
3 changed files with 206 additions and 7 deletions

View file

@ -1,9 +1,50 @@
<script lang="ts">
import { viewStore } from '$lib/stores/view.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import { format } from 'date-fns';
import { de } from 'date-fns/locale';
import CalendarHeaderContextMenu from './CalendarHeaderContextMenu.svelte';
// Format title based on view type
let contextMenu: CalendarHeaderContextMenu;
// Get weekday format string based on setting
function getWeekdayFormat(): string {
switch (settingsStore.headerWeekdayFormat) {
case 'full':
return 'EEEE';
case 'short':
return 'EEE';
case 'hidden':
return '';
}
}
// Get date format string based on settings
function getDateFormat(includeYear: boolean = true): string {
const parts: string[] = [];
// Weekday
const weekdayFormat = getWeekdayFormat();
if (weekdayFormat) {
parts.push(weekdayFormat);
}
// Date with optional month
if (settingsStore.headerShowDate) {
if (settingsStore.headerAlwaysShowMonth) {
parts.push(includeYear ? 'd.M. MMMM yyyy' : 'd.M.');
} else {
parts.push(includeYear ? 'd. MMMM yyyy' : 'd.');
}
} else if (includeYear) {
// Only month and year if date is hidden
parts.push('MMMM yyyy');
}
return parts.join(', ');
}
// Format title based on view type and settings
let title = $derived.by(() => {
const date = viewStore.currentDate;
const rangeStart = viewStore.viewRange.start;
@ -11,23 +52,26 @@
// Helper to format date range
const formatRange = () => {
const showMonth = settingsStore.headerAlwaysShowMonth;
const startFormat = showMonth ? 'd.M.' : 'd.';
if (rangeStart.getMonth() === rangeEnd.getMonth()) {
return (
format(rangeStart, 'd.', { locale: de }) +
format(rangeStart, startFormat, { locale: de }) +
' - ' +
format(rangeEnd, 'd. MMMM yyyy', { locale: de })
format(rangeEnd, showMonth ? 'd.M. MMMM yyyy' : 'd. MMMM yyyy', { locale: de })
);
}
return (
format(rangeStart, 'd. MMM', { locale: de }) +
format(rangeStart, showMonth ? 'd.M. MMM' : 'd. MMM', { locale: de }) +
' - ' +
format(rangeEnd, 'd. MMM yyyy', { locale: de })
format(rangeEnd, showMonth ? 'd.M. MMM yyyy' : 'd. MMM yyyy', { locale: de })
);
};
switch (viewStore.viewType) {
case 'day':
return format(date, 'EEEE, d. MMMM yyyy', { locale: de });
return format(date, getDateFormat(true), { locale: de });
case '5day':
case 'week':
case '10day':
@ -43,16 +87,29 @@
return format(date, 'MMMM yyyy', { locale: de });
}
});
function handleContextMenu(e: MouseEvent) {
e.preventDefault();
contextMenu.show(e.clientX, e.clientY);
}
</script>
<header class="calendar-header">
<header
class="calendar-header"
class:compact={settingsStore.headerCompact}
oncontextmenu={handleContextMenu}
role="banner"
>
<h1 class="header-title">{title}</h1>
</header>
<CalendarHeaderContextMenu bind:this={contextMenu} />
<style>
.calendar-header {
padding: 0.75rem 1rem;
background: transparent;
cursor: context-menu;
}
.header-title {
@ -67,4 +124,19 @@
font-size: 1rem;
}
}
/* Compact variant */
.calendar-header.compact {
padding: 0.5rem 1rem;
}
.calendar-header.compact .header-title {
font-size: 1rem;
}
@media (max-width: 640px) {
.calendar-header.compact .header-title {
font-size: 0.875rem;
}
}
</style>

View file

@ -0,0 +1,102 @@
<script lang="ts">
import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import { ArrowsIn, TextAa, Calendar, CalendarBlank } from '@manacore/shared-icons';
import { settingsStore, type WeekdayFormat } from '$lib/stores/settings.svelte';
// Context menu state
let visible = $state(false);
let x = $state(0);
let y = $state(0);
// Build menu items based on current settings
let menuItems = $derived.by((): ContextMenuItem[] => {
return [
{
id: 'compact',
label: 'Kompakte Ansicht',
icon: ArrowsIn,
toggle: true,
checked: settingsStore.headerCompact,
action: () => toggleSetting('headerCompact'),
},
{
id: 'divider-1',
label: '',
type: 'divider',
},
{
id: 'weekday-full',
label: 'Wochentag ausgeschrieben',
icon: TextAa,
toggle: true,
checked: settingsStore.headerWeekdayFormat === 'full',
action: () => setWeekdayFormat('full'),
},
{
id: 'weekday-short',
label: 'Wochentag gekürzt',
icon: TextAa,
toggle: true,
checked: settingsStore.headerWeekdayFormat === 'short',
action: () => setWeekdayFormat('short'),
},
{
id: 'weekday-hidden',
label: 'Wochentag ausblenden',
icon: TextAa,
toggle: true,
checked: settingsStore.headerWeekdayFormat === 'hidden',
action: () => setWeekdayFormat('hidden'),
},
{
id: 'divider-2',
label: '',
type: 'divider',
},
{
id: 'show-date',
label: 'Datum anzeigen',
icon: Calendar,
toggle: true,
checked: settingsStore.headerShowDate,
action: () => toggleSetting('headerShowDate'),
},
{
id: 'always-show-month',
label: 'Monat immer anzeigen',
icon: CalendarBlank,
toggle: true,
checked: settingsStore.headerAlwaysShowMonth,
action: () => toggleSetting('headerAlwaysShowMonth'),
},
];
});
function toggleSetting(key: keyof typeof settingsStore.settings) {
const currentValue = settingsStore.settings[key];
if (typeof currentValue === 'boolean') {
settingsStore.set(key, !currentValue);
}
}
function setWeekdayFormat(format: WeekdayFormat) {
settingsStore.set('headerWeekdayFormat', format);
}
function handleClose() {
visible = false;
}
// Export show function to be called from parent
export function show(clientX: number, clientY: number) {
x = clientX;
y = clientY;
visible = true;
}
export function hide() {
visible = false;
}
</script>
<ContextMenu {visible} {x} {y} items={menuItems} onClose={handleClose} />

View file

@ -13,6 +13,7 @@ import { userSettings } from './user-settings.svelte';
export type WeekStartDay = 0 | 1; // 0 = Sunday, 1 = Monday
export type TimeFormat = '24h' | '12h';
export type AllDayDisplayMode = 'header' | 'block'; // header = separate row, block = full day block in grid
export type WeekdayFormat = 'full' | 'short' | 'hidden';
export interface CalendarAppSettings {
// View settings
@ -26,6 +27,12 @@ export interface CalendarAppSettings {
dayEndHour: number; // Last visible hour (0-23)
allDayDisplayMode: AllDayDisplayMode; // How to display all-day events
// Header settings
headerCompact: boolean; // Compact header display
headerWeekdayFormat: WeekdayFormat; // Weekday display format
headerShowDate: boolean; // Show date in header
headerAlwaysShowMonth: boolean; // Always show month (e.g., "13.12.")
// DateStrip settings
dateStripShowMoonPhases: boolean; // Show moon phase indicators
dateStripShowEventIndicators: boolean; // Show event dot indicators
@ -61,6 +68,11 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
dayStartHour: 6,
dayEndHour: 20,
allDayDisplayMode: 'header',
// Header defaults
headerCompact: false,
headerWeekdayFormat: 'full',
headerShowDate: true,
headerAlwaysShowMonth: false,
// DateStrip defaults
dateStripShowMoonPhases: true,
dateStripShowEventIndicators: true,
@ -175,6 +187,19 @@ export const settingsStore = {
get allDayDisplayMode() {
return settings.allDayDisplayMode;
},
// Header settings
get headerCompact() {
return settings.headerCompact;
},
get headerWeekdayFormat() {
return settings.headerWeekdayFormat;
},
get headerShowDate() {
return settings.headerShowDate;
},
get headerAlwaysShowMonth() {
return settings.headerAlwaysShowMonth;
},
// DateStrip settings
get dateStripShowMoonPhases() {
return settings.dateStripShowMoonPhases;