Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b27f335
add BitFullCalendar extras component #12449
msynk Jun 13, 2026
df593ab
make theme compatible
msynk Jun 16, 2026
fdc0e72
rename css classes and move code blocks to code-behind
msynk Jun 17, 2026
64e2d84
Merge branch 'develop' into 12449-blazorui-fullcalendar-extras
msynk Jun 20, 2026
6b55009
fix a lot of issues
msynk Jun 20, 2026
0124500
resolve review comments
msynk Jun 21, 2026
b48fff7
resolve review comments II
msynk Jun 21, 2026
fd3e6fb
resolve review comments III
msynk Jun 21, 2026
4a9d1de
resolve review comments IV
msynk Jun 21, 2026
897f9cc
resolve review comments V
msynk Jun 21, 2026
44ef70c
resolve review comments VI
msynk Jun 21, 2026
4339767
fix demo
msynk Jun 21, 2026
6551fb9
resolve review comments VII
msynk Jun 21, 2026
cc3d16b
resolve review comments VIII
msynk Jun 21, 2026
692dd36
resolve review comments IX
msynk Jun 22, 2026
18435b9
resolve review comments X
msynk Jun 22, 2026
f181910
resolve review comments XI
msynk Jun 22, 2026
b01f279
resolve review comments XII
msynk Jun 22, 2026
d790e39
resolve review comments XIII
msynk Jun 22, 2026
1c23485
resolve review comments XIV
msynk Jun 22, 2026
f498381
resolve review comments XV
msynk Jun 22, 2026
318c8d5
add new features
msynk Jun 22, 2026
de8a32a
resolve review comments XVI
msynk Jun 23, 2026
b7746a6
resolve review comments XVII
msynk Jun 23, 2026
7087703
resolve review comments XVIII
msynk Jun 23, 2026
6a7360b
resolve review comments XIX
msynk Jun 23, 2026
e8e16eb
resolve review comments XX
msynk Jun 23, 2026
41c0b78
resolve review comments XXI
msynk Jun 24, 2026
955181c
Merge branch 'develop' into 12449-blazorui-fullcalendar-extras
msynk Jun 24, 2026
2d5bbd1
resolve review comments XXII
msynk Jun 24, 2026
dff228a
resolve review comments XXIII
msynk Jun 24, 2026
45b8421
resolve review comments XXIV
msynk Jun 24, 2026
345c4a7
resolve review commnets XXV
msynk Jun 25, 2026
eda294c
resolve review comments XXVI
msynk Jun 26, 2026
dc60be6
resolve review comments XXVII
msynk Jun 26, 2026
cd3294a
resolve review comments XXVIII
msynk Jun 26, 2026
be8a448
resolve review comments XXIX
msynk Jun 26, 2026
1db96b8
resolve review comments XXX
msynk Jun 26, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@namespace Bit.BlazorUI
@implements IDisposable

<div class="bit-bfc-body" @key="(State.Mode, State.View)">
@if (State.Mode == BitFullCalendarMode.Timeline)
{
switch (State.View)
{
case BitFullCalendarView.Day:
<BitFcTimelineDayView Events="State.Events.ToList()" EventTemplate="TimelineEventTemplate" />
break;
case BitFullCalendarView.Week:
<BitFcTimelineWeekView Events="State.Events.ToList()" EventTemplate="TimelineEventTemplate" />
break;
case BitFullCalendarView.Month:
<BitFcTimelineMonthView Events="State.Events.ToList()" EventTemplate="TimelineEventTemplate" />
break;
default:
<BitFcTimelineWeekView Events="State.Events.ToList()" EventTemplate="TimelineEventTemplate" />
break;
}
}
else
{
switch (State.View)
{
case BitFullCalendarView.Month:
<BitFcCalendarMonthView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="MonthEventTemplate" />
break;
case BitFullCalendarView.Week:
<BitFcCalendarWeekView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="WeekEventTemplate" />
break;
case BitFullCalendarView.Day:
<BitFcCalendarDayView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="DayEventTemplate" />
break;
case BitFullCalendarView.Year:
<BitFcCalendarYearView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" />
break;
case BitFullCalendarView.Agenda:
<BitFcAgendaEvents />
break;
}
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Bit.BlazorUI;

public partial class BitFcCalendarBody
{
[CascadingParameter] public BitFullCalendarState State { get; set; } = default!;

[Parameter] public RenderFragment<BitFullCalendarEvent>? MonthEventTemplate { get; set; }
[Parameter] public RenderFragment<BitFullCalendarEvent>? WeekEventTemplate { get; set; }
[Parameter] public RenderFragment<BitFullCalendarEvent>? DayEventTemplate { get; set; }
[Parameter] public RenderFragment<BitFullCalendarEvent>? TimelineEventTemplate { get; set; }

private List<BitFullCalendarEvent> _singleDayEvents = [];
private List<BitFullCalendarEvent> _multiDayEvents = [];

protected override void OnInitialized()
{
State.OnStateChanged += Refresh;
ComputeEvents();
}

private void Refresh()
{
ComputeEvents();
InvokeAsync(StateHasChanged);
}

private void ComputeEvents()
{
_singleDayEvents = State.Events.Where(e => e.IsSingleDay).ToList();
_multiDayEvents = State.Events.Where(e => e.IsMultiDay).ToList();
}

public void Dispose() => State.OnStateChanged -= Refresh;
Comment thread
msynk marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@namespace Bit.BlazorUI

<div class="bit-bfc-toast-container">
@foreach (var toast in _toasts)
{
<div class="bit-bfc-toast @(toast.IsError ? "error" : "success")">
@toast.Message
</div>
Comment thread
msynk marked this conversation as resolved.
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Bit.BlazorUI;

public partial class BitFcCalendarToast
{
private readonly List<ToastItem> _toasts = [];
private int _nextId;
Comment thread
msynk marked this conversation as resolved.

public void Show(string message, bool isError = false)
{
var item = new ToastItem { Id = _nextId++, Message = message, IsError = isError };
_toasts.Add(item);
StateHasChanged();
_ = RemoveAfterDelay(item.Id);
}

private async Task RemoveAfterDelay(int id)
{
await Task.Delay(3000);
_toasts.RemoveAll(t => t.Id == id);
await InvokeAsync(StateHasChanged);
}
Comment thread
msynk marked this conversation as resolved.

private class ToastItem
{
public int Id { get; set; }
public string Message { get; set; } = "";
public bool IsError { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@namespace Bit.BlazorUI

<BitCascadingValueProvider ValueList="BuildCascadingValues()">
<div class="bit-bfc" dir="@(State.IsRtl ? "rtl" : "ltr")">
<BitFcCalendarHeader />
<BitFcCalendarBody MonthEventTemplate="MonthEventTemplate"
WeekEventTemplate="WeekEventTemplate"
DayEventTemplate="DayEventTemplate"
TimelineEventTemplate="TimelineEventTemplate" />
<BitFcCalendarToast />
</div>
</BitCascadingValueProvider>
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System.Globalization;

namespace Bit.BlazorUI;

public partial class BitFullCalendar : IDisposable
{
/// <summary>
/// Events displayed in the calendar. Assign a list from parent state; updates are synced on each
/// render when the reference or contents change. User-driven add, edit, and delete actions are
/// reported through <see cref="OnChange"/> - update this list (or your backing store) in the handler
/// to keep the UI in sync.
/// </summary>
[Parameter] public List<BitFullCalendarEvent>? Events { get; set; }

/// <summary>
/// Culture for the calendar. Accepts any CultureInfo, e.g. new CultureInfo("fa-IR").
/// NOTE: do NOT use this parameter when the component is rendered with
/// @rendermode="InteractiveServer" - CultureInfo is not JSON-serializable.
/// Use <see cref="CultureName"/> instead for server-interactive scenarios.
/// </summary>
[Parameter] public CultureInfo? Culture { get; set; }

/// <summary>
/// Culture name string (e.g. "fa-IR", "ar-SA", "fr-FR").
/// Preferred over <see cref="Culture"/> when using @rendermode="InteractiveServer"
/// because plain strings are safely serialized by Blazor's parameter persistence.
/// When both are supplied, CultureName takes precedence.
/// Blazor WebAssembly hosts must set <c>BlazorWebAssemblyLoadAllGlobalizationData</c> to
/// <c>true</c> (or load a custom ICU shard) for cultures outside the default EFIGS/CJK shards.
/// </summary>
[Parameter] public string? CultureName { get; set; }

/// <summary>
/// Localized strings for calendar UI labels, buttons, dialogs, filters, and accessibility text.
/// Defaults to English; override individual properties on a <see cref="BitFullCalendarTexts"/>
/// instance to localize the component without replacing built-in dialogs.
/// </summary>
[Parameter] public BitFullCalendarTexts Texts { get; set; } = new();

/// <summary>
/// Ordered list of event colors shown in pickers, filters, agenda headers, badges, and bullets.
/// Each entry has its own <see cref="BitFullCalendarColorOption.Id"/> (matched against
/// <see cref="BitFullCalendarEvent.Color"/>), <see cref="BitFullCalendarColorOption.Title"/>
/// (the display name shown verbatim - for example <c>"SkyBlue"</c>), and
/// <see cref="BitFullCalendarColorOption.Value"/> (any CSS color value used for swatches and badges).
/// When <c>null</c> or empty, <see cref="BitFullCalendarColorOption.Defaults"/> is used.
/// </summary>
[Parameter] public IReadOnlyList<BitFullCalendarColorOption>? EventColorOptions { get; set; }

/// <summary>
/// Resources displayed as rows in the resource timeline view. When <c>null</c> or empty,
/// the resource timeline tab is hidden from the header. Each event's
/// <see cref="BitFullCalendarEvent.Resource"/> is matched against the resource <c>Id</c>.
/// </summary>
[Parameter] public IReadOnlyList<BitFullCalendarResource>? Resources { get; set; }

/// <summary>
/// Raised when a user adds, edits, or deletes an event in the calendar UI.
/// </summary>
[Parameter] public EventCallback<BitFullCalendarChangeEventArgs> OnChange { get; set; }

/// <summary>
/// When assigned, the built-in add dialog is suppressed. The callback receives a draft
/// <see cref="BitFullCalendarEvent"/> with <see cref="BitFullCalendarEvent.StartDate"/> and
/// <see cref="BitFullCalendarEvent.EndDate"/> set from the interaction (for example the clicked day/week slot);
/// <see cref="BitFullCalendarEvent.Id"/> is empty and other fields are left at defaults.
/// Consumers should show their own UI and
/// raise <see cref="OnChange"/> (or mutate <see cref="Events"/> bound to parent state) after persisting changes.
/// </summary>
[Parameter] public EventCallback<BitFullCalendarEvent?> OnAddClick { get; set; }

/// <summary>
/// When assigned, the built-in event details dialog is suppressed when an event is clicked.
/// The callback receives the clicked <see cref="BitFullCalendarEvent"/>. Consumers should
/// show their own event details UI. This applies to all views (day, week, month, agenda) and
/// to multi-day event rows and event list dialogs.
/// </summary>
[Parameter] public EventCallback<BitFullCalendarEvent> OnEventClick { get; set; }

/// <summary>
/// Raised when the visible date range changes - for example when the user navigates
/// with prev/next/today buttons or switches views. The callback receives the inclusive
/// start and end dates of the new range together with the active view.
/// </summary>
[Parameter] public EventCallback<BitFullCalendarDateChangeEventArgs> OnDateChange { get; set; }

/// <summary>
/// Optional template for customizing event rendering in the day view.
/// When provided, replaces the default event card content inside the time-grid blocks.
/// </summary>
[Parameter] public RenderFragment<BitFullCalendarEvent>? DayEventTemplate { get; set; }

/// <summary>
/// Optional template for customizing event rendering in the week view.
/// When provided, replaces the default event card content inside the time-grid blocks.
/// </summary>
[Parameter] public RenderFragment<BitFullCalendarEvent>? WeekEventTemplate { get; set; }

/// <summary>
/// Optional template for customizing event rendering in the month view.
/// When provided, replaces the default event badge content inside month grid cells.
/// </summary>
[Parameter] public RenderFragment<BitFullCalendarEvent>? MonthEventTemplate { get; set; }

/// <summary>
/// Optional template for customizing event rendering in the resource timeline view.
/// When provided, replaces the default event card content inside the timeline blocks.
/// </summary>
[Parameter] public RenderFragment<BitFullCalendarEvent>? TimelineEventTemplate { get; set; }

/// <summary>
/// When <c>true</c>, the built-in color and attendee filter dropdowns are hidden from the calendar header.
/// Consumers can provide their own external filter UI and pass pre-filtered events to the calendar.
/// </summary>
[Parameter] public bool HideFilters { get; set; }

/// <summary>
/// When <c>true</c>, the built-in settings gear button is hidden from the calendar header.
/// Consumers can still drive settings programmatically through the <see cref="Options"/> object.
/// </summary>
[Parameter] public bool HideSettings { get; set; }

/// <summary>
/// Configuration options controlling initial calendar preferences
/// such as dark mode, time format, badge variant, day start hour, and agenda grouping.
/// Values are applied when the component initializes or when a new instance is assigned.
/// </summary>
[Parameter] public BitFullCalendarOptions Options { get; set; } = new();

/// <summary>
/// Initial layout mode. <see cref="BitFullCalendarMode.Event"/> shows the standard
/// day/week/month/year/agenda views. <see cref="BitFullCalendarMode.Timeline"/> switches to
/// the resource × time layout (day, week, month) and requires <see cref="Resources"/> to contain
/// at least one entry; otherwise the Timeline tab and mode have no effect.
/// </summary>
[Parameter] public BitFullCalendarMode? InitialMode { get; set; }

public BitFullCalendarState State { get; set; } = new();
private BitFullCalendarChangeNotifier _changeNotifier = default!;
private BitFullCalendarColorScheme _colorScheme = new(null);
private BitFullCalendarOptions? _appliedOptions;

private BitCascadingValueList BuildCascadingValues() => new()
{
{ State },
{ Texts },
{ _changeNotifier },
{ _colorScheme },
{ Options },
{ HideFilters, "HideFilters" },
{ HideSettings, "HideSettings" },
{ OnAddClick, "OnAddClick" },
{ OnEventClick, "OnEventClick" },
};

private CultureInfo ResolveCulture() =>
CultureName is { Length: > 0 } name
? new CultureInfo(name)
: Culture ?? CultureInfo.CurrentUICulture;

protected override void OnInitialized()
{
State.Initialize(Events ?? [], ResolveCulture());
ApplyOptions();
if (InitialMode.HasValue)
State.SetMode(InitialMode.Value);
_changeNotifier = new BitFullCalendarChangeNotifier(State, args => OnChange.InvokeAsync(args));
Comment thread
msynk marked this conversation as resolved.
Outdated
State.OnStateChanged += HandleStateChanged;
State.OnDateRangeChanged += HandleDateRangeChanged;
}

protected override void OnParametersSet()
{
_colorScheme = new BitFullCalendarColorScheme(EventColorOptions);
var resolved = ResolveCulture();
if (!string.Equals(resolved.Name, State.Culture.Name, StringComparison.Ordinal))
State.SetCulture(resolved);
Comment thread
msynk marked this conversation as resolved.
Outdated

if (Events is not null)
State.SyncEvents(Events);
Comment thread
msynk marked this conversation as resolved.
Outdated

State.SyncResources(Resources);

ApplyOptions();
}
Comment thread
msynk marked this conversation as resolved.

private void ApplyOptions()
{
if (ReferenceEquals(Options, _appliedOptions))
return;

_appliedOptions = Options;
State.SetUse24HourFormat(Options.Use24HourFormat);
State.SetBadgeVariant(Options.BadgeVariant);
State.SetStartOfDayHour(Options.StartOfDayHour);
State.SetAgendaModeGroupBy(Options.AgendaModeGroupBy);
State.SetEventLayout(Options.EventLayout);
}

private void HandleStateChanged() => InvokeAsync(StateHasChanged);

private void HandleDateRangeChanged(BitFullCalendarDateChangeEventArgs args)
{
InvokeAsync(() => OnDateChange.InvokeAsync(args));
}



public void Dispose()
{
State.OnStateChanged -= HandleStateChanged;
State.OnDateRangeChanged -= HandleDateRangeChanged;
}
}
Loading
Loading