refactor(calendar): extract birthday popover logic into composable

Create useBirthdayPopover composable to eliminate duplicated birthday
popover state and handlers across DayView, WeekView, and MonthView.

Changes:
- Add useBirthdayPopover.svelte.ts composable
- Update DayView to use the composable
- Update WeekView to use the composable
- Update MonthView to use the composable
- Export from composables/index.ts

This removes ~40 lines of duplicated code across the three views.

🤖 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 21:18:50 +01:00
parent e0ef15276d
commit 668957a30b
5 changed files with 76 additions and 66 deletions

View file

@ -6,12 +6,9 @@
import { searchStore } from '$lib/stores/search.svelte';
import { todosStore, type Task } from '$lib/stores/todos.svelte';
import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte';
import { birthdaysStore, type BirthdayEvent } from '$lib/stores/birthdays.svelte';
import { birthdaysStore } from '$lib/stores/birthdays.svelte';
import BirthdayPopover from '$lib/components/birthday/BirthdayPopover.svelte';
import {
useVisibleHours,
useCurrentTimeIndicator,
} from '$lib/composables/useVisibleHours.svelte';
import { useVisibleHours, useCurrentTimeIndicator, useBirthdayPopover } from '$lib/composables';
import { toDate } from '$lib/utils/eventDateHelpers';
import { HOUR_HEIGHT_PX, SNAP_INTERVAL_MINUTES } from '$lib/utils/calendarConstants';
import {
@ -102,9 +99,8 @@
let blockAllDayEvents = $derived(allDayEvents.filter((e) => getEventDisplayMode(e) === 'block'));
// Birthday Popover State
let selectedBirthday = $state<BirthdayEvent | null>(null);
let birthdayPopoverPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
// Birthday Popover (using composable)
const birthdayPopover = useBirthdayPopover();
// Get birthdays for current day (if enabled in settings)
let birthdays = $derived.by(() => {
@ -112,18 +108,6 @@
return birthdaysStore.getBirthdaysForDay(viewStore.currentDate);
});
// Handle birthday click - show popover
function handleBirthdayClick(birthday: BirthdayEvent, e: MouseEvent) {
e.stopPropagation();
selectedBirthday = birthday;
birthdayPopoverPosition = { x: e.clientX, y: e.clientY };
}
// Close birthday popover
function closeBirthdayPopover() {
selectedBirthday = null;
}
// ============================================================================
// Drag & Drop State
// ============================================================================
@ -744,7 +728,7 @@
{#each birthdays as birthday}
<button
class="all-day-event birthday-event"
onclick={(e) => handleBirthdayClick(birthday, e)}
onclick={(e) => birthdayPopover.handleBirthdayClick(birthday, e)}
>
🎂 {birthday.displayName}
{#if settingsStore.showBirthdayAge && birthday.age > 0}
@ -915,11 +899,11 @@
<EventContextMenu onEdit={handleContextMenuEdit} />
<!-- Birthday Popover -->
{#if selectedBirthday}
{#if birthdayPopover.selectedBirthday}
<BirthdayPopover
birthday={selectedBirthday}
position={birthdayPopoverPosition}
onClose={closeBirthdayPopover}
birthday={birthdayPopover.selectedBirthday}
position={birthdayPopover.popoverPosition}
onClose={birthdayPopover.closePopover}
/>
{/if}

View file

@ -9,6 +9,7 @@
import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte';
import TodoDayCell from './TodoDayCell.svelte';
import BirthdayPopover from '$lib/components/birthday/BirthdayPopover.svelte';
import { useBirthdayPopover } from '$lib/composables';
import { goto } from '$app/navigation';
import {
format,
@ -267,23 +268,12 @@
// ============================================================================
// Birthday Functions
// ============================================================================
let selectedBirthday = $state<BirthdayEvent | null>(null);
let birthdayPopoverPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
const birthdayPopover = useBirthdayPopover();
function getBirthdaysForDay(day: Date): BirthdayEvent[] {
if (!settingsStore.showBirthdays) return [];
return birthdaysStore.getBirthdaysForDay(day);
}
function handleBirthdayClick(birthday: BirthdayEvent, e: MouseEvent) {
e.stopPropagation();
selectedBirthday = birthday;
birthdayPopoverPosition = { x: e.clientX, y: e.clientY };
}
function closeBirthdayPopover() {
selectedBirthday = null;
}
</script>
<div class="month-view" style="--column-count: {columnCount}" bind:this={monthViewRef}>
@ -368,7 +358,7 @@
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="event-pill birthday-pill"
onclick={(e) => handleBirthdayClick(birthday, e)}
onclick={(e) => birthdayPopover.handleBirthdayClick(birthday, e)}
role="button"
tabindex="0"
>
@ -396,11 +386,11 @@
</div>
<!-- Birthday Popover -->
{#if selectedBirthday}
{#if birthdayPopover.selectedBirthday}
<BirthdayPopover
birthday={selectedBirthday}
position={birthdayPopoverPosition}
onClose={closeBirthdayPopover}
birthday={birthdayPopover.selectedBirthday}
position={birthdayPopover.popoverPosition}
onClose={birthdayPopover.closePopover}
/>
{/if}

View file

@ -8,10 +8,7 @@
import { birthdaysStore, type BirthdayEvent } from '$lib/stores/birthdays.svelte';
import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte';
import BirthdayPopover from '$lib/components/birthday/BirthdayPopover.svelte';
import {
useVisibleHours,
useCurrentTimeIndicator,
} from '$lib/composables/useVisibleHours.svelte';
import { useVisibleHours, useCurrentTimeIndicator, useBirthdayPopover } from '$lib/composables';
import { toDate } from '$lib/utils/eventDateHelpers';
import { HOUR_HEIGHT_PX, SNAP_INTERVAL_MINUTES } from '$lib/utils/calendarConstants';
import {
@ -127,9 +124,8 @@
// Reference to the days container for position calculations
let daysContainerEl: HTMLDivElement;
// Birthday Popover State
let selectedBirthday = $state<BirthdayEvent | null>(null);
let birthdayPopoverPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
// Birthday Popover (using composable)
const birthdayPopover = useBirthdayPopover();
// Get birthdays for a day (if enabled in settings)
function getBirthdaysForDay(day: Date): BirthdayEvent[] {
@ -142,18 +138,6 @@
settingsStore.showBirthdays && days.some((day) => getBirthdaysForDay(day).length > 0)
);
// Handle birthday click - show popover
function handleBirthdayClick(birthday: BirthdayEvent, e: MouseEvent) {
e.stopPropagation();
selectedBirthday = birthday;
birthdayPopoverPosition = { x: e.clientX, y: e.clientY };
}
// Close birthday popover
function closeBirthdayPopover() {
selectedBirthday = null;
}
function getEventsForDay(day: Date): CalendarEvent[] {
return getVisibleTimedEvents(
eventsStore.getEventsForDay(day),
@ -909,7 +893,7 @@
{#each getBirthdaysForDay(day) as birthday}
<button
class="all-day-event birthday-event"
onclick={(e) => handleBirthdayClick(birthday, e)}
onclick={(e) => birthdayPopover.handleBirthdayClick(birthday, e)}
>
🎂 {birthday.displayName}
{#if settingsStore.showBirthdayAge && birthday.age > 0}
@ -1116,11 +1100,11 @@
<EventContextMenu onEdit={handleContextMenuEdit} />
<!-- Birthday Popover -->
{#if selectedBirthday}
{#if birthdayPopover.selectedBirthday}
<BirthdayPopover
birthday={selectedBirthday}
position={birthdayPopoverPosition}
onClose={closeBirthdayPopover}
birthday={birthdayPopover.selectedBirthday}
position={birthdayPopover.popoverPosition}
onClose={birthdayPopover.closePopover}
/>
{/if}

View file

@ -23,6 +23,9 @@ export { useSidebarDrop, type SidebarDropConfig } from './useSidebarDrop.svelte'
// Keyboard handling
export { useCalendarKeyboard, type CancellableOperation } from './useCalendarKeyboard.svelte';
// Birthday popover management
export { useBirthdayPopover } from './useBirthdayPopover.svelte';
// Legacy exports (kept for backwards compatibility, may be removed later)
export { useDragDrop, type DragDropConfig, type DragState } from './useDragDrop.svelte';
export { useResize, type ResizeConfig, type ResizeState } from './useResize.svelte';

View file

@ -0,0 +1,49 @@
/**
* Birthday Popover Composable
* Manages birthday popover state and handlers for calendar views
*/
import type { BirthdayEvent } from '$lib/api/birthdays';
export function useBirthdayPopover() {
let selectedBirthday = $state<BirthdayEvent | null>(null);
let popoverPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 });
/**
* Handle click on a birthday indicator to show the popover
*/
function handleBirthdayClick(birthday: BirthdayEvent, e: MouseEvent) {
e.stopPropagation();
selectedBirthday = birthday;
popoverPosition = { x: e.clientX, y: e.clientY };
}
/**
* Close the birthday popover
*/
function closePopover() {
selectedBirthday = null;
}
/**
* Check if popover is currently open
*/
function isOpen(): boolean {
return selectedBirthday !== null;
}
return {
// State (reactive getters)
get selectedBirthday() {
return selectedBirthday;
},
get popoverPosition() {
return popoverPosition;
},
// Methods
handleBirthdayClick,
closePopover,
isOpen,
};
}