Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ The component has 3 public methods that can help navigate forward and backward t
- `goToNextMonth`
- `goToCurrentMonth`

Phase 2 adds mobile-focused week/day navigation methods:
- `goToPreviousWeek`
- `goToNextWeek`
- `goToPreviousDay`
- `goToNextDay`
- `goToToday`
- `setViewMode('month'|'week'|'day')`

You can use these methods on extra views using `before-calendar-view` or `after-calendar-view` explained below.

### Advanced usage
Expand Down
Binary file added docs/screenshots/phase2/after-desktop-1280.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/phase2/after-mobile-375.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/phase2/before-desktop-1280.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/phase2/before-mobile-375.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 57 additions & 24 deletions resources/views/calendar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,72 @@
@elseif($pollMillis !== null)
wire:poll.{{ $pollMillis }}ms
@endif
x-data="{ touchStartX: 0, touchStartY: 0 }"
@if($swipeNavigationEnabled)
x-on:touchstart="touchStartX = $event.touches[0].clientX; touchStartY = $event.touches[0].clientY"
x-on:touchend="
const dx = $event.changedTouches[0].clientX - touchStartX;
const dy = $event.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) {
if (dx > 0) {
@if($viewMode === 'month') $wire.goToPreviousMonth(); @elseif($viewMode === 'week') $wire.goToPreviousWeek(); @else $wire.goToPreviousDay(); @endif
} else {
@if($viewMode === 'month') $wire.goToNextMonth(); @elseif($viewMode === 'week') $wire.goToNextWeek(); @else $wire.goToNextDay(); @endif
}
}
"
@endif
>
<div>
@includeIf($beforeCalendarView)
</div>
@if($mobileHeaderEnabled)
<div class="sticky top-0 z-10 border-b border-gray-100 bg-white/95 px-3 py-2 backdrop-blur-sm md:hidden">
<div class="flex items-center justify-between">
<button
@if($viewMode === 'month') wire:click="goToPreviousMonth" @elseif($viewMode === 'week') wire:click="goToPreviousWeek" @else wire:click="goToPreviousDay" @endif
class="-ml-2 min-h-[44px] min-w-[44px] p-2 text-gray-700"
aria-label="Previous"
>
</button>

<div class="flex">
<div class="overflow-x-auto w-full">
<div class="inline-block min-w-full overflow-hidden">
<button
wire:click="goToToday"
class="text-base font-semibold text-gray-900"
>
{{ $headerLabel }}
</button>

<div class="w-full flex flex-row">
@foreach($monthGrid->first() as $day)
@include($dayOfWeekView, ['day' => $day])
@endforeach
</div>
<button
@if($viewMode === 'month') wire:click="goToNextMonth" @elseif($viewMode === 'week') wire:click="goToNextWeek" @else wire:click="goToNextDay" @endif
class="-mr-2 min-h-[44px] min-w-[44px] p-2 text-gray-700"
aria-label="Next"
>
</button>
</div>

@foreach($monthGrid as $week)
<div class="w-full flex flex-row">
@foreach($week as $day)
@include($dayView, [
'componentId' => $componentId,
'day' => $day,
'dayInMonth' => $day->isSameMonth($startsAt),
'isToday' => $day->isToday(),
'events' => $getEventsForDay($day, $events),
])
@endforeach
</div>
<div class="mt-2 grid grid-cols-3 rounded-lg bg-gray-100 p-1 text-xs font-medium">
@foreach(['month' => 'Month', 'week' => 'Week', 'day' => 'Day'] as $mode => $label)
<button wire:click="setViewMode('{{ $mode }}')" class="rounded-md px-2 py-1.5 {{ $viewMode === $mode ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-600' }}">
{{ $label }}
</button>
@endforeach
</div>
</div>
@endif

<div class="hidden md:block">
@includeIf($beforeCalendarView)
</div>

<div>
@if($viewMode === 'month')
@include('livewire-calendar::month')
@elseif($viewMode === 'week')
@include('livewire-calendar::week')
@else
@include('livewire-calendar::day-detail')
@endif

<div class="hidden md:block">
@includeIf($afterCalendarView)
</div>
</div>
51 changes: 51 additions & 0 deletions resources/views/day-detail.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<div class="md:hidden px-3 pb-3">
<div class="rounded-xl border border-gray-200 bg-white">
<div class="border-b border-gray-100 px-3 py-2 text-sm font-semibold text-gray-800">
{{ $selectedDate->format('l, F j') }}
</div>

@if($allDayEvents->isNotEmpty())
<div class="border-b border-gray-100 px-3 py-2">
<p class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">All day</p>
<div class="mt-2 space-y-2">
@foreach($allDayEvents as $event)
<div @if($eventClickEnabled) wire:click.stop="onEventClick('{{ $event['id'] }}')" @endif class="rounded-lg border border-gray-100 p-2">
<p class="text-sm font-medium text-gray-900">{{ $event['title'] }}</p>
</div>
@endforeach
</div>
</div>
@endif

<div class="max-h-[520px] overflow-y-auto p-3 space-y-2">
@forelse($timedDayEvents as $event)
<div @if($eventClickEnabled) wire:click.stop="onEventClick('{{ $event['id'] }}')" @endif class="rounded-lg border border-gray-100 p-3">
<p class="text-sm font-medium text-gray-900 truncate">{{ $event['title'] }}</p>
<p class="mt-1 text-xs text-gray-500">{{ $event['start_time'] ?? 'All day' }}{{ isset($event['end_time']) ? ' - '.$event['end_time'] : '' }}</p>
</div>
@empty
<p class="text-sm text-gray-500">No events for this day.</p>
@endforelse
</div>
</div>
</div>

<div class="hidden md:block border border-gray-200 bg-white">
<div class="border-b border-gray-100 p-3 text-sm font-semibold text-gray-800">{{ $selectedDate->format('l, F j, Y') }}</div>
<div class="max-h-[720px] overflow-y-auto">
@foreach($hours as $hour)
@php($slotEvents = $getEventsForHour($selectedDate, $hourIndexMap[$hour], $timedDayEvents))
<div class="grid grid-cols-[80px_1fr] border-t border-gray-100">
<div class="p-2 text-xs text-gray-500">{{ $hour }}</div>
<div class="min-h-[64px] p-2">
@foreach($slotEvents as $event)
<div class="mb-2 rounded border border-blue-100 bg-blue-50 px-2 py-2 text-sm text-blue-900">
<p class="font-medium">{{ $event['title'] }}</p>
<p class="text-xs text-blue-700">{{ $event['start_time'] ?? '' }}{{ isset($event['end_time']) ? ' - '.$event['end_time'] : '' }}</p>
</div>
@endforeach
</div>
</div>
@endforeach
</div>
</div>
23 changes: 23 additions & 0 deletions resources/views/month.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="flex">
<div class="w-full overflow-hidden">
<div class="w-full flex flex-row">
@foreach($monthGrid->first() as $day)
@include($dayOfWeekView, ['day' => $day])
@endforeach
</div>

@foreach($monthGrid as $week)
<div class="w-full flex flex-row">
@foreach($week as $day)
@include($dayView, [
'componentId' => $componentId,
'day' => $day,
'dayInMonth' => $day->isSameMonth($startsAt),
'isToday' => $day->isToday(),
'events' => $getEventsForDay($day, $events),
])
@endforeach
</div>
@endforeach
</div>
</div>
66 changes: 66 additions & 0 deletions resources/views/week.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<div class="md:hidden px-3 pb-3">
<div class="flex gap-2 overflow-x-auto snap-x snap-mandatory">
@foreach($weekDays as $day)
@php($dayEvents = $getEventsForDay($day, $events))
<button
wire:click="selectDate({{ $day->year }}, {{ $day->month }}, {{ $day->day }})"
class="snap-start min-w-[76px] rounded-lg border px-2 py-2 text-left {{ $selectedDate->isSameDay($day) ? 'bg-blue-50 border-blue-300' : 'bg-white border-gray-200' }}"
>
<p class="text-[10px] uppercase tracking-wide text-gray-500">{{ $day->format('D') }}</p>
<p class="text-sm font-semibold {{ $day->isToday() ? 'text-blue-600' : 'text-gray-900' }}">{{ $day->format('j') }}</p>
<p class="text-[10px] text-gray-500 mt-1">{{ $dayEvents->count() }} {{ \Illuminate\Support\Str::plural('event', $dayEvents->count()) }}</p>
</button>
@endforeach
</div>

<div class="mt-3 rounded-xl border border-gray-200 bg-white">
<div class="border-b border-gray-100 px-3 py-2 text-sm font-semibold text-gray-800">
{{ $selectedDate->format('l, M j') }}
</div>
<div class="max-h-[420px] overflow-y-auto p-3 space-y-2">
@forelse($selectedWeekEvents as $event)
<div
@if($eventClickEnabled)
wire:click.stop="onEventClick('{{ $event['id'] }}')"
@endif
class="rounded-lg border border-gray-100 p-3"
>
<p class="text-sm font-medium text-gray-900 truncate">{{ $event['title'] }}</p>
@if(isset($event['start_time']) || isset($event['end_time']))
<p class="mt-1 text-xs text-gray-500">{{ $event['start_time'] ?? 'All day' }}{{ isset($event['end_time']) ? ' - '.$event['end_time'] : '' }}</p>
@endif
</div>
@empty
<p class="text-sm text-gray-500">No events for this day.</p>
@endforelse
</div>
</div>
</div>

<div class="hidden md:block">
<div class="grid grid-cols-8 border border-gray-200 bg-white text-sm">
<div class="border-r border-gray-200 p-2 text-xs font-semibold text-gray-500">Time</div>
@foreach($weekDays as $day)
<div class="border-r border-gray-200 p-2 text-center {{ $day->isToday() ? 'bg-blue-50' : '' }}">
<p class="text-xs text-gray-500">{{ $day->format('D') }}</p>
<p class="font-semibold">{{ $day->format('j') }}</p>
</div>
@endforeach
</div>

<div class="max-h-[720px] overflow-y-auto border-x border-b border-gray-200">
@foreach($hours as $hour)
<div class="grid grid-cols-8 border-t border-gray-100">
<div class="border-r border-gray-200 p-2 text-xs text-gray-500">{{ $hour }}</div>
@foreach($weekDays as $day)
@php($slotEvents = $getEventsForHour($day, $hourIndexMap[$hour], $events))
<div class="min-h-[64px] border-r border-gray-100 p-1">
@foreach($slotEvents as $event)
<div class="mb-1 rounded border border-blue-100 bg-blue-50 px-2 py-1 text-xs text-blue-900 truncate">{{ $event['title'] }}</div>
@endforeach
</div>
@endforeach
</div>
@endforeach
</div>
</div>
Loading