initial calendar prototype

This commit is contained in:
2026-02-03 16:06:19 +13:00
parent 9955a898b2
commit 2fbefaa419
2 changed files with 305 additions and 19 deletions

View File

@@ -1,26 +1,310 @@
<script lang="ts"> <script lang="ts">
import { Calendar } from '@fullcalendar/core' let {
import multiMonthPlugin from '@fullcalendar/multimonth' year = $bindable('2026'),
import { onMount } from 'svelte'; month = $bindable('0')
} = $props()
let calendarEl = $state() var headers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
let days = $state([]);
let expanded = $state(false);
let currentYear = $state(parseInt(year));
let currentMonth = $state(parseInt(month));
onMount(() => { // Get current week when in week view
const calendar = new Calendar(calendarEl, { function getCurrentWeek() {
plugins: [multiMonthPlugin], const today = new Date();
initialView: 'multiMonthYear', const currentDay = today.getDay();
multiMonthMaxColumns: 1, // force a single column const diff = currentDay === 0 ? -6 : 1 - currentDay; // Adjust for Monday start
headerToolbar: {
left: 'prev,next', const monday = new Date(today);
center: 'title', monday.setDate(today.getDate() + diff);
right: 'today'
}, const week = [];
}); for (let i = 0; i < 7; i++) {
calendar.render(); const day = new Date(monday);
day.setDate(monday.getDate() + i);
week.push({
date: day.getDate(),
month: day.getMonth(),
year: day.getFullYear(),
isCurrentMonth: day.getMonth() === today.getMonth(),
isToday: day.toDateString() === today.toDateString()
});
}
return week;
}
// Generate calendar for a full month
function generateMonth(year: number, month: number) {
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const daysInMonth = lastDay.getDate();
// Get the day of week for first day (0 = Sunday, adjust to Monday = 0)
let startDay = firstDay.getDay() - 1;
if (startDay === -1) startDay = 6; // Sunday becomes 6
const days = [];
// Previous month days
const prevMonthLastDay = new Date(year, month, 0).getDate();
for (let i = startDay - 1; i >= 0; i--) {
days.push({
date: prevMonthLastDay - i,
month: month - 1,
year: month === 0 ? year - 1 : year,
isCurrentMonth: false,
isToday: false
});
}
// Current month days
const today = new Date();
for (let i = 1; i <= daysInMonth; i++) {
const isToday = today.getDate() === i &&
today.getMonth() === month &&
today.getFullYear() === year;
days.push({
date: i,
month: month,
year: year,
isCurrentMonth: true,
isToday
});
}
// Next month days to fill the grid
const remaining = 42 - days.length; // 6 rows * 7 days
for (let i = 1; i <= remaining; i++) {
days.push({
date: i,
month: month + 1,
year: month === 11 ? year + 1 : year,
isCurrentMonth: false,
isToday: false
});
}
return days;
}
// Update days when expanded state or month changes
$effect(() => {
if (expanded) {
days = generateMonth(currentYear, currentMonth);
} else {
days = getCurrentWeek();
}
}); });
function toggleExpanded() {
expanded = !expanded;
if (expanded) {
// Reset to current date's month when expanding
const today = new Date();
currentYear = today.getFullYear();
currentMonth = today.getMonth();
}
}
function previousMonth() {
if (!expanded) return;
if (currentMonth === 0) {
currentMonth = 11;
currentYear--;
} else {
currentMonth--;
}
}
function nextMonth() {
if (!expanded) return;
if (currentMonth === 11) {
currentMonth = 0;
currentYear++;
} else {
currentMonth++;
}
}
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
</script> </script>
<div class="text-black bg-white/30 p-4 rounded-2xl" bind:this={calendarEl}> <div class="calendar-container">
<div class="calendar-header">
<button class="expand-btn" onclick={toggleExpanded} title={expanded ? "Show week view" : "Show month view"}>
{expanded ? '' : '+'}
</button>
{#if expanded}
<button class="nav-btn" onclick={previousMonth}></button>
<span class="month-year">{monthNames[currentMonth]} {currentYear}</span>
<button class="nav-btn" onclick={nextMonth}></button>
{:else}
<span class="month-year">Current Week</span>
{/if}
</div>
<div class="calendar">
{#each headers as header}
<span class="day-name">{header}</span>
{/each}
{#each days as day}
<div class="day" class:other-month={!day.isCurrentMonth} class:today={day.isToday}>
<span class="date">{day.date}</span>
</div>
{/each}
</div>
</div> </div>
<style>
.calendar-container {
width: 100%;
}
.calendar-header {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background: rgba(166, 168, 179, 0.05);
border-bottom: 1px solid rgba(166, 168, 179, 0.12);
}
.expand-btn {
width: 36px;
height: 36px;
border: 1px solid rgba(166, 168, 179, 0.2);
background: transparent;
color: #e9a1a7;
font-size: 24px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.expand-btn:hover {
background: rgba(233, 161, 167, 0.1);
border-color: #e9a1a7;
}
.nav-btn {
width: 32px;
height: 32px;
border: 1px solid rgba(166, 168, 179, 0.2);
background: transparent;
color: #e9a1a7;
font-size: 20px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.nav-btn:hover {
background: rgba(233, 161, 167, 0.1);
border-color: #e9a1a7;
}
.month-year {
font-size: 18px;
font-weight: 600;
color: #e9a1a7;
flex: 1;
text-align: center;
}
.calendar {
display: grid;
width: 100%;
grid-template-columns: repeat(7, minmax(120px, 1fr));
grid-template-rows: 50px;
grid-auto-rows: 120px;
overflow: auto;
}
.day {
border-bottom: 1px solid rgba(166, 168, 179, 0.12);
border-right: 1px solid rgba(166, 168, 179, 0.12);
text-align: right;
padding: 14px 20px;
letter-spacing: 1px;
font-size: 14px;
box-sizing: border-box;
color: #98a0a6;
position: relative;
z-index: 1;
}
.day.other-month {
opacity: 0.3;
}
.day.today {
background: rgba(233, 161, 167, 0.1);
}
.day.today .date {
color: #e9a1a7;
font-weight: 700;
}
.day:nth-of-type(7n + 7) {
border-right: 0;
}
.day:nth-of-type(n + 1):nth-of-type(-n + 7) {
grid-row: 1;
}
.day:nth-of-type(n + 8):nth-of-type(-n + 14) {
grid-row: 2;
}
.day:nth-of-type(n + 15):nth-of-type(-n + 21) {
grid-row: 3;
}
.day:nth-of-type(n + 22):nth-of-type(-n + 28) {
grid-row: 4;
}
.day:nth-of-type(n + 29):nth-of-type(-n + 35) {
grid-row: 5;
}
.day:nth-of-type(n + 36):nth-of-type(-n + 42) {
grid-row: 6;
}
.day:nth-of-type(7n + 1) {
grid-column: 1/1;
}
.day:nth-of-type(7n + 2) {
grid-column: 2/2;
}
.day:nth-of-type(7n + 3) {
grid-column: 3/3;
}
.day:nth-of-type(7n + 4) {
grid-column: 4/4;
}
.day:nth-of-type(7n + 5) {
grid-column: 5/5;
}
.day:nth-of-type(7n + 6) {
grid-column: 6/6;
}
.day:nth-of-type(7n + 7) {
grid-column: 7/7;
}
.day-name {
font-size: 12px;
text-transform: uppercase;
color: #e9a1a7;
text-align: center;
border-bottom: 1px solid rgba(166, 168, 179, 0.12);
line-height: 50px;
font-weight: 500;
}
</style>

View File

@@ -17,6 +17,8 @@
} }
</script> </script>
<Calendar />
<div class="flex flex-col md:flex-row space-y-4 w-full mt-6"> <div class="flex flex-col md:flex-row space-y-4 w-full mt-6">
<div class="md:w-1/2 md:order-2"> <div class="md:w-1/2 md:order-2">