diff --git a/apps/calendar/apps/web/src/lib/components/calendar/DateStripFab.svelte b/apps/calendar/apps/web/src/lib/components/calendar/DateStripFab.svelte
new file mode 100644
index 000000000..94eedf059
--- /dev/null
+++ b/apps/calendar/apps/web/src/lib/components/calendar/DateStripFab.svelte
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/calendar/apps/web/src/lib/stores/calendars.svelte.ts b/apps/calendar/apps/web/src/lib/stores/calendars.svelte.ts
index 1f8fc7515..bf9489a31 100644
--- a/apps/calendar/apps/web/src/lib/stores/calendars.svelte.ts
+++ b/apps/calendar/apps/web/src/lib/stores/calendars.svelte.ts
@@ -4,18 +4,42 @@
import type { Calendar, CreateCalendarInput, UpdateCalendarInput } from '@calendar/shared';
import * as api from '$lib/api/calendars';
+import { BIRTHDAY_CALENDAR } from '$lib/api/birthdays';
+import { settingsStore } from './settings.svelte';
// State
let calendars = $state([]);
let loading = $state(false);
let error = $state(null);
+// Virtual birthday calendar (created dynamically based on settings)
+const birthdayCalendar: Calendar = {
+ id: BIRTHDAY_CALENDAR.id,
+ userId: '',
+ name: BIRTHDAY_CALENDAR.name,
+ color: BIRTHDAY_CALENDAR.color,
+ isDefault: false,
+ isVisible: true, // Visibility controlled by settingsStore.showBirthdays
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+};
+
// Helper to safely get calendars array (Svelte 5 runes safety)
function getCalendarsArray(): Calendar[] {
const arr = calendars ?? [];
return Array.isArray(arr) ? arr : [];
}
+// Derived: all calendars including virtual birthday calendar
+const allCalendars = $derived.by(() => {
+ const userCalendars = getCalendarsArray();
+ // Add virtual birthday calendar if birthdays are enabled in settings
+ if (settingsStore.showBirthdays) {
+ return [...userCalendars, { ...birthdayCalendar, isVisible: true }];
+ }
+ return userCalendars;
+});
+
// Derived: visible calendars
const visibleCalendars = $derived(getCalendarsArray().filter((c) => c.isVisible));
@@ -30,6 +54,9 @@ export const calendarsStore = {
get calendars() {
return calendars;
},
+ get allCalendars() {
+ return allCalendars;
+ },
get visibleCalendars() {
return visibleCalendars;
},
@@ -42,6 +69,9 @@ export const calendarsStore = {
get error() {
return error;
},
+ get birthdayCalendarId() {
+ return BIRTHDAY_CALENDAR.id;
+ },
/**
* Fetch all calendars
@@ -143,7 +173,26 @@ export const calendarsStore = {
* Get calendar color by ID (with fallback)
*/
getColor(id: string) {
+ // Handle virtual birthday calendar
+ if (id === BIRTHDAY_CALENDAR.id) {
+ return BIRTHDAY_CALENDAR.color;
+ }
const calendar = getCalendarsArray().find((c) => c.id === id);
return calendar?.color || '#3b82f6';
},
+
+ /**
+ * Toggle birthday calendar visibility
+ * (This updates the settings store, not the calendar itself)
+ */
+ toggleBirthdaysVisibility() {
+ settingsStore.set('showBirthdays', !settingsStore.showBirthdays);
+ },
+
+ /**
+ * Check if a calendar ID is the virtual birthday calendar
+ */
+ isBirthdayCalendar(id: string) {
+ return id === BIRTHDAY_CALENDAR.id;
+ },
};
diff --git a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte
index fcc0688c6..52066a92b 100644
--- a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte
+++ b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte
@@ -52,6 +52,7 @@
import CalendarToolbar from '$lib/components/calendar/CalendarToolbar.svelte';
import CalendarToolbarContent from '$lib/components/calendar/CalendarToolbarContent.svelte';
import DateStrip from '$lib/components/calendar/DateStrip.svelte';
+ import DateStripFab from '$lib/components/calendar/DateStripFab.svelte';
import EventContextMenu from '$lib/components/event/EventContextMenu.svelte';
import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte';
@@ -387,7 +388,11 @@
{#if showCalendarToolbar}
-
+ {#if settingsStore.dateStripCollapsed}
+
+ {:else}
+
+ {/if}
{/if}
@@ -433,6 +438,7 @@
? '140px'
: '70px'}
hasFabRight={showCalendarToolbar && !isSidebarMode}
+ hasFabLeft={showCalendarToolbar && !isSidebarMode && settingsStore.dateStripCollapsed}
/>
@@ -517,4 +523,32 @@
flex: 1;
min-height: 0;
}
+
+ /* Adjust InputBar when FABs are visible (toolbar FAB on right, DateStripFab on left) */
+ /* For a centered InputBar with max-width 450px, left edge is at 50% - 225px */
+ /* DateStripFab is positioned at: 50% - 225px - 8px gap - 54px fab width */
+ /* Note: In sidebar mode, InputBar uses default 700px max-width */
+ :global(.quick-input-bar.has-fab-right .input-container),
+ :global(.quick-input-bar.has-fab-left .input-container) {
+ max-width: 450px;
+ }
+
+ /* On smaller screens (<900px), FABs move to fixed positions (left: 1rem, right: 1rem) */
+ @media (max-width: 900px) {
+ :global(.quick-input-bar.has-fab-right .input-container) {
+ max-width: calc(100% - 140px); /* 54px FAB + padding */
+ margin-left: auto;
+ margin-right: 0;
+ }
+ :global(.quick-input-bar.has-fab-left .input-container) {
+ max-width: calc(100% - 140px); /* 54px FAB + padding */
+ margin-left: 0;
+ margin-right: auto;
+ }
+ :global(.quick-input-bar.has-fab-right.has-fab-left .input-container) {
+ max-width: calc(100% - 200px); /* Both FABs */
+ margin-left: auto;
+ margin-right: auto;
+ }
+ }