From db701fd6489522aea6d926890631406ec6880339 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:26:54 +0100 Subject: [PATCH] fix(calendar): improve DateStrip today and range highlighting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use inline styles for today highlighting to ensure visibility - Add hardcoded blue color (#3b82f6) for today indicator - Fix view range highlighting with light blue background - Add proper scroll container with max-width constraint - Improve overall styling with box-shadow and border 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../lib/components/calendar/DateStrip.svelte | 159 ++++++------------ 1 file changed, 51 insertions(+), 108 deletions(-) diff --git a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte index f357b7a8d..413c58e47 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte @@ -16,24 +16,9 @@ let viewRange = $derived(viewStore.viewRange); let currentDate = $derived(viewStore.currentDate); - // Check if a day is within the current view range - function isInViewRange(day: Date): boolean { - return isWithinInterval(day, { start: viewRange.start, end: viewRange.end }); - } - - // Check if day is the first in the view range - function isFirstInRange(day: Date): boolean { - return isSameDay(day, viewRange.start); - } - - // Check if day is the last in the view range - function isLastInRange(day: Date): boolean { - return isSameDay(day, viewRange.end); - } - // How many days to load in each direction const DAYS_BUFFER = 60; - const LOAD_THRESHOLD = 20; // Load more when within this many days of edge + const LOAD_THRESHOLD = 20; // Generate initial days centered around current date let startDate = $state(subDays(startOfDay(new Date()), DAYS_BUFFER)); @@ -67,7 +52,6 @@ async function scrollToDate(date: Date) { await tick(); - // First ensure the date is in our range const targetDate = startOfDay(date); if (targetDate < startDate) { startDate = subDays(targetDate, DAYS_BUFFER); @@ -103,17 +87,11 @@ isLoadingMore = true; if (direction === 'past') { - // Save scroll position relative to a reference element - const firstVisibleDay = scrollContainer?.querySelector('.day-item'); const scrollLeftBefore = scrollContainer?.scrollLeft || 0; - startDate = subDays(startDate, DAYS_BUFFER); await tick(); - - // Restore scroll position - if (firstVisibleDay && scrollContainer) { - const newScrollLeft = scrollLeftBefore + DAYS_BUFFER * 54; // 54px is approximate day width (52px + gap) - scrollContainer.scrollLeft = newScrollLeft; + if (scrollContainer) { + scrollContainer.scrollLeft = scrollLeftBefore + DAYS_BUFFER * 54; } } else { endDate = addDays(endDate, DAYS_BUFFER); @@ -125,7 +103,7 @@ function checkTodayVisibility() { if (!scrollContainer) return; - const todayElement = scrollContainer.querySelector('.day-item.today'); + const todayElement = scrollContainer.querySelector('[data-is-today="true"]'); if (!todayElement) { isTodayVisible = false; return; @@ -134,7 +112,6 @@ const containerRect = scrollContainer.getBoundingClientRect(); const todayRect = todayElement.getBoundingClientRect(); - // Check if today element is within the visible scroll area isTodayVisible = todayRect.left >= containerRect.left - 20 && todayRect.right <= containerRect.right + 20; } @@ -142,34 +119,25 @@ function handleScroll() { if (!scrollContainer || isLoadingMore) return; - // Check if today is visible checkTodayVisibility(); - const { scrollLeft, scrollWidth, clientWidth } = scrollContainer; - const scrollRight = scrollWidth - scrollLeft - clientWidth; - - // Calculate approximate day index from scroll position - const dayWidth = 54; // approximate (52px + gap) + const { scrollLeft, clientWidth } = scrollContainer; + const dayWidth = 54; const visibleDayIndex = Math.floor(scrollLeft / dayWidth); const totalDays = days.length; - // Load more past days if (visibleDayIndex < LOAD_THRESHOLD) { loadMoreDays('past'); } - // Load more future days if (totalDays - visibleDayIndex - Math.floor(clientWidth / dayWidth) < LOAD_THRESHOLD) { loadMoreDays('future'); } } - // Get month label for a date (shown at month boundaries) function getMonthLabel(day: Date, index: number): string | null { - // Show month label on first day of month or first day in view if (day.getDate() === 1 || index === 0) { if (day.getMonth() === 0 && day.getDate() === 1) { - // Show year for January 1st return format(day, 'MMM yyyy', { locale: de }); } return format(day, 'MMM', { locale: de }); @@ -178,26 +146,22 @@ } onMount(() => { - // Initial scroll to current date scrollToDate(viewStore.currentDate); });
- {#if !isTodayVisible} {/if}
-
{#each days as day, index} {@const monthLabel = getMonthLabel(day, index)} {@const dayIsToday = isToday(day)} {@const dayIsSelected = isSameDay(day, currentDate)} {@const dayIsWeekend = day.getDay() === 0 || day.getDay() === 6} - {@const dayIsFirstOfMonth = day.getDate() === 1} {@const dayInRange = isWithinInterval(day, { start: viewRange.start, end: viewRange.end })} {@const dayIsRangeStart = isSameDay(day, viewRange.start)} {@const dayIsRangeEnd = isSameDay(day, viewRange.end)} @@ -208,18 +172,24 @@ {/if} {/each}
@@ -244,57 +214,48 @@ align-items: center; justify-content: center; padding: 0.5rem 1rem; - background: var(--color-primary); + background: #3b82f6; border: none; border-radius: 9999px; cursor: pointer; - color: var(--color-primary-foreground); + color: white; font-size: 0.8125rem; font-weight: 600; white-space: nowrap; transition: all 0.2s ease; pointer-events: auto; margin-bottom: 0.5rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - animation: fade-in 0.2s ease; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); } .today-btn-floating:hover { - background: color-mix(in srgb, var(--color-primary) 90%, transparent); + background: #2563eb; transform: scale(1.05); } - @keyframes fade-in { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - .date-strip-container { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: transparent; - width: 100%; + background: var(--color-surface, #ffffff); + border-radius: 16px; + margin: 0 1rem; + padding: 0.5rem; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); + border: 1px solid var(--color-border, #e5e7eb); pointer-events: auto; + max-width: calc(100vw - 2rem); + overflow: hidden; } .days-scroll { display: flex; align-items: center; - gap: 0.125rem; + gap: 2px; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; scroll-behavior: auto; - flex: 1; - padding: 0.25rem 0; + padding: 0.25rem; } .days-scroll::-webkit-scrollbar { @@ -313,10 +274,10 @@ font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-primary); + color: #3b82f6; white-space: nowrap; padding: 0.25rem 0.5rem; - background: color-mix(in srgb, var(--color-primary) 10%, transparent); + background: rgba(59, 130, 246, 0.1); border-radius: 4px; } @@ -332,64 +293,45 @@ border: none; border-radius: 10px; cursor: pointer; - color: var(--color-foreground); + color: var(--color-foreground, #1f2937); transition: all 0.15s ease; flex-shrink: 0; } .day-item:hover { - background: var(--color-muted); - opacity: 0.7; + background: var(--color-muted, #f3f4f6); } .day-item.weekend { - color: var(--color-muted-foreground); + color: var(--color-muted-foreground, #6b7280); } - /* Today - always highlighted with primary color */ - .day-item.today { - background: var(--color-primary) !important; - color: var(--color-primary-foreground) !important; - border-radius: 10px !important; - font-weight: 700; - } - - .day-item.today:hover { - opacity: 0.9; - } - - .day-item.selected:not(.today) { - background: var(--color-muted); - color: var(--color-primary); + .day-item.selected { + background: var(--color-muted, #f3f4f6); + color: #3b82f6; font-weight: 600; } /* View range highlighting */ - .day-item.in-range:not(.today) { - background: var(--color-muted); + .day-item.in-range { + background: rgba(59, 130, 246, 0.15); border-radius: 0; } - .day-item.in-range.range-start:not(.today) { + .day-item.in-range.range-start { border-radius: 10px 0 0 10px; } - .day-item.in-range.range-end:not(.today) { + .day-item.in-range.range-end { border-radius: 0 10px 10px 0; } - .day-item.in-range.range-start.range-end:not(.today) { + .day-item.in-range.range-start.range-end { border-radius: 10px; } - .day-item.in-range:not(.today):not(.selected):hover { - background: var(--color-border); - } - - .day-item.first-of-month:not(.today):not(.selected):not(.in-range) { - border-left: 2px solid var(--color-border); - border-radius: 0 8px 8px 0; - margin-left: 0.25rem; + .day-item.in-range:hover { + background: rgba(59, 130, 246, 0.25); } .day-weekday { @@ -409,7 +351,8 @@ /* Responsive */ @media (max-width: 640px) { .date-strip-container { - padding: 0.5rem 0.5rem; + margin: 0 0.5rem; + padding: 0.375rem; } .today-btn-floating {