From shiny-maui-scheduler
Generate .NET MAUI scheduler views, event providers, and calendar/agenda/list components using Shiny.Maui.Scheduler
npx claudepluginhub shinyorg/scheduler --plugin shiny-maui-schedulerThis skill uses the workspace's default tool permissions.
You are an expert in Shiny.Maui.Scheduler, a .NET MAUI component library providing calendar, agenda timeline, and event list views.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Automates semantic versioning and release workflow for Claude Code plugins: bumps versions in package.json, marketplace.json, plugin.json; verifies builds; creates git tags, GitHub releases, changelogs.
You are an expert in Shiny.Maui.Scheduler, a .NET MAUI component library providing calendar, agenda timeline, and event list views.
Invoke this skill when the user wants to:
ISchedulerEventProvider to supply event dataSchedulerCalendarView, SchedulerAgendaView, or SchedulerCalendarListViewGitHub: https://github.com/shinyorg/scheduler
NuGet: Shiny.Maui.Scheduler
Namespace: Shiny.Maui.Scheduler
Targets: net10.0-android, net10.0-ios, net10.0-maccatalyst, net10.0-windows
Shiny.Maui.Scheduler provides three views that share a common ISchedulerEventProvider interface:
All views are built programmatically (no XAML internals), use AOT-safe lambda bindings, and support custom DataTemplates.
dotnet add package Shiny.Maui.Scheduler
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseShinyScheduler();
public interface ISchedulerEventProvider
{
Task<IReadOnlyList<SchedulerEvent>> GetEvents(DateTimeOffset start, DateTimeOffset end);
void OnEventSelected(SchedulerEvent selectedEvent);
bool CanCalendarSelect(DateOnly selectedDate);
void OnCalendarDateSelected(DateOnly selectedDate);
void OnAgendaTimeSelected(DateTimeOffset selectedTime);
bool CanSelectAgendaTime(DateTimeOffset selectedTime);
}
public class SchedulerEvent
{
public string Identifier { get; set; } // Default: new Guid
public string Title { get; set; }
public string? Description { get; set; }
public Color? Color { get; set; }
public bool IsAllDay { get; set; }
public DateTimeOffset Start { get; set; }
public DateTimeOffset End { get; set; }
}
Used for custom DayHeaderTemplate bindings in SchedulerCalendarListView:
public class CalendarListDayGroup : List<SchedulerEvent>
{
public DateOnly Date { get; }
public string DateDisplay { get; } // "dddd, MMMM d, yyyy"
public bool IsToday { get; }
public string EventCountDisplay { get; } // "3 events"
}
Used for custom DayPickerItemTemplate bindings in SchedulerAgendaView:
public class DatePickerItemContext
{
public DateOnly Date { get; set; }
public string DayNumber { get; set; } // "30"
public string DayName { get; set; } // "MON"
public string MonthName { get; set; } // "MAR"
public bool IsSelected { get; set; }
public bool IsToday { get; set; }
}
Used for custom OverflowItemTemplate bindings in SchedulerCalendarView:
public class CalendarOverflowContext
{
public int EventCount { get; set; }
public DateOnly Date { get; set; }
}
When generating code for Shiny.Maui.Scheduler projects, follow these conventions:
CRITICAL: Never use string-based SetBinding(). Always use the static lambda overload:
// Correct - AOT safe
label.SetBinding(Label.TextProperty, static (SchedulerEvent e) => e.Title);
// WRONG - not AOT safe
label.SetBinding(Label.TextProperty, "Title");
Always implement all methods of ISchedulerEventProvider:
public class MyEventProvider : ISchedulerEventProvider
{
public async Task<IReadOnlyList<SchedulerEvent>> GetEvents(DateTimeOffset start, DateTimeOffset end)
{
// Fetch events from your data source for the given range
// Multi-day events should be included if they overlap the range at all
return events;
}
public void OnEventSelected(SchedulerEvent selectedEvent)
{
// Handle event taps — navigate to detail, show dialog, etc.
}
public bool CanCalendarSelect(DateOnly selectedDate) => true;
public void OnCalendarDateSelected(DateOnly selectedDate) { }
public void OnAgendaTimeSelected(DateTimeOffset selectedTime) { }
public bool CanSelectAgendaTime(DateTimeOffset selectedTime) => true;
}
Pass the provider to views via the Provider bindable property — it does not need to be registered in DI.
Use INotifyPropertyChanged (not ObservableObject from CommunityToolkit — this library has no CommunityToolkit dependency):
public class MySchedulerViewModel : INotifyPropertyChanged
{
DateOnly _selectedDate = DateOnly.FromDateTime(DateTime.Today);
public MySchedulerViewModel(ISchedulerEventProvider provider)
{
Provider = provider;
}
public ISchedulerEventProvider Provider { get; }
public DateOnly SelectedDate
{
get => _selectedDate;
set { _selectedDate = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler? PropertyChanged;
void OnPropertyChanged([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Register ViewModels and Pages as transient:
builder.Services.AddTransient<MyViewModel>();
builder.Services.AddTransient<MyPage>();
Use XAML with x:DataType for compile-time binding:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scheduler="clr-namespace:Shiny.Maui.Scheduler;assembly=Shiny.Maui.Scheduler"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Pages.MyPage"
x:DataType="vm:MyViewModel"
Title="My Page">
<scheduler:SchedulerCalendarListView
Provider="{Binding Provider}"
SelectedDate="{Binding SelectedDate}" />
</ContentPage>
Code-behind with constructor injection:
public partial class MyPage : ContentPage
{
public MyPage(MyViewModel viewModel)
{
BindingContext = viewModel;
InitializeComponent();
}
}
| Property | Type | Default |
|---|---|---|
| Provider | ISchedulerEventProvider? | null |
| SelectedDate | DateOnly | Today (TwoWay) |
| DisplayMonth | DateOnly | Today (TwoWay) |
| MinDate | DateOnly? | null |
| MaxDate | DateOnly? | null |
| ShowCalendarCellEventCountOnly | bool | false |
| EventItemTemplate | DataTemplate? | null |
| OverflowItemTemplate | DataTemplate? | null |
| LoaderTemplate | DataTemplate? | null |
| MaxEventsPerCell | int | 3 |
| CalendarCellColor | Color | White |
| CalendarCellSelectedColor | Color | LightBlue |
| CurrentDayColor | Color | DodgerBlue |
| FirstDayOfWeek | DayOfWeek | Sunday |
| AllowPan | bool | true |
| AllowZoom | bool | false |
| Property | Type | Default |
|---|---|---|
| Provider | ISchedulerEventProvider? | null |
| SelectedDate | DateOnly | Today (TwoWay) |
| MinDate | DateOnly? | null |
| MaxDate | DateOnly? | null |
| DaysToShow | int | 1 (clamped 1-7) |
| ShowCarouselDatePicker | bool | true |
| ShowCurrentTimeMarker | bool | true |
| Use24HourTime | bool | true |
| EventItemTemplate | DataTemplate? | null |
| LoaderTemplate | DataTemplate? | null |
| DayPickerItemTemplate | DataTemplate? | null |
| CurrentTimeMarkerColor | Color | Red |
| TimezoneColor | Color | Gray |
| SeparatorColor | Color | Light gray |
| DefaultEventColor | Color | CornflowerBlue |
| TimeSlotHeight | double | 60.0 |
| AllowPan | bool | true |
| AllowZoom | bool | false |
| ShowAdditionalTimezones | bool | false |
| AdditionalTimezones | IList<TimeZoneInfo> | empty |
| Property | Type | Default |
|---|---|---|
| Provider | ISchedulerEventProvider? | null |
| SelectedDate | DateOnly | Today (TwoWay) |
| MinDate | DateOnly? | null |
| MaxDate | DateOnly? | null |
| EventItemTemplate | DataTemplate? | null |
| DayHeaderTemplate | DataTemplate? | null |
| LoaderTemplate | DataTemplate? | null |
| DaysPerPage | int | 30 |
| DefaultEventColor | Color | CornflowerBlue |
| DayHeaderBackgroundColor | Color | Transparent |
| DayHeaderTextColor | Color | Black |
| AllowPan | bool | true |
| AllowZoom | bool | false |
The DefaultTemplates static class provides reusable templates:
| Method | Binds To |
|---|---|
CreateEventItemTemplate() | SchedulerEvent — Color bar + title |
CreateOverflowTemplate() | CalendarOverflowContext — "+N more" |
CreateLoaderTemplate() | None — ActivityIndicator + "Loading..." |
CreateCalendarListDayHeaderTemplate() | CalendarListDayGroup — Accent bar + today dot + bold date + event count |
CreateCalendarListEventItemTemplate() | SchedulerEvent — Card with color bar, title, description, start-end time range |
CreateAppleCalendarDayPickerTemplate() | DatePickerItemContext — Apple Calendar-style day picker (default for Agenda) |
var myEventTemplate = new DataTemplate(() =>
{
var grid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition(new GridLength(4)),
new ColumnDefinition(GridLength.Star)
},
Padding = new Thickness(8),
ColumnSpacing = 8
};
var colorBar = new BoxView { CornerRadius = 2 };
colorBar.SetBinding(BoxView.ColorProperty, static (SchedulerEvent e) => e.Color);
var title = new Label { FontSize = 14 };
title.SetBinding(Label.TextProperty, static (SchedulerEvent e) => e.Title);
grid.Add(colorBar, 0);
grid.Add(title, 1);
return grid;
});
ContentView — they can be placed anywhere in your XAML layoutSelectedDate is TwoWay on all views — changes propagate back to the ViewModelMinDate/MaxDate to constrain navigation and selectionAllowPan and AllowZoom to control gesture interactionsLoaderTemplate for custom loading indicators (defaults to ActivityIndicator + "Loading...")GetEvents() may be called with any date range — implement it to handle arbitrary rangesSchedulerCalendarListView skips empty days (only days with events are shown)SchedulerAgendaView uses an Apple Calendar-style day picker by default; override with DayPickerItemTemplate using DatePickerItemContextSchedulerAgendaView supports 24-hour and 12-hour (AM/PM) time via Use24HourTime; this affects time labels, event time text, and the current time markerSchedulerAgendaView supports multiple timezone columns via AdditionalTimezones + ShowAdditionalTimezones; in multi-day view, time labels appear once on the left with sticky per-day date headers above each column; timezone abbreviations only show in headers when multiple timezones are visibleSchedulerAgendaView separator lines are styleable via SeparatorColorAdditionalTimezones is an IList<TimeZoneInfo> — add timezones in code-behind: AgendaView.AdditionalTimezones.Add(TimeZoneInfo.FindSystemTimeZoneById("America/New_York"))