Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 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
184b89b
resolve review comments XXXI
msynk Jun 26, 2026
09ce880
resolve review comments XXXII
msynk Jun 27, 2026
3ecd6bc
resolve review comments XXXIII
msynk Jun 27, 2026
51b2092
resolve review comments XXXIV
msynk Jun 27, 2026
481b51d
resolve review comments XXXV
msynk Jun 27, 2026
1913900
resolve review comments XXXVI
msynk Jun 27, 2026
c40f7c0
resolve review comments XXXVI
msynk Jun 27, 2026
1316b58
resolve review comments XXXVIII
msynk Jun 27, 2026
d7699c4
resolve review comments XXXIX
msynk Jun 27, 2026
bfb89be
resolve review comments XXXX
msynk Jun 27, 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="_timelineEvents" EventTemplate="TimelineEventTemplate" />
break;
case BitFullCalendarView.Week:
<BitFcTimelineWeekView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" />
break;
case BitFullCalendarView.Month:
<BitFcTimelineMonthView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" />
break;
default:
<BitFcTimelineWeekView Events="_timelineEvents" 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,36 @@
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 = [];
private List<BitFullCalendarEvent> _timelineEvents = [];

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();
_timelineEvents = State.Events.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" aria-live="polite">
@foreach (var toast in _toasts)
{
<div @key="toast.Id" class="bit-bfc-toast @(toast.IsError ? "error" : "success")" role="@(toast.IsError ? "alert" : "status")">
@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,126 @@
namespace Bit.BlazorUI;

public partial class BitFcCalendarToast : IAsyncDisposable
{
private readonly List<ToastItem> _toasts = [];
private readonly List<CancellationTokenSource> _removalTokens = [];
private readonly object _removalTokensLock = new();
private int _nextId;
Comment thread
msynk marked this conversation as resolved.

public void Show(string message, bool isError = false)
{
// Allocate the id atomically: Show can be invoked off the renderer thread (the list mutation
// is marshalled below, but the id is assigned here on the caller's thread), so a plain
// _nextId++ could hand out duplicate ids under concurrent calls and break RemoveAfterDelay's
// per-id removal.
var item = new ToastItem { Id = Interlocked.Increment(ref _nextId), Message = message, IsError = isError };

var cts = new CancellationTokenSource();
Comment thread
msynk marked this conversation as resolved.
// Capture the token up front, before DisposeAsync (which can run concurrently and dispose
// the source) gets a chance to. Reading cts.Token later inside RemoveAfterDelay would risk
// an ObjectDisposedException if the component is torn down before the delay is queued.
var token = cts.Token;
lock (_removalTokensLock)
{
_removalTokens.Add(cts);
}

// Marshal the list mutation and render onto the renderer's dispatcher so the whole toast
// lifecycle (add here, remove in RemoveAfterDelay) stays dispatcher-safe even when Show is
// invoked from a non-renderer thread.
_ = InvokeAsync(() =>
{
// DisposeAsync cancels every queued token; if it ran between scheduling this callback
// and it executing, bail out before touching _toasts/StateHasChanged so no UI work (or
// RemoveAfterDelay timer) starts after the component has been torn down.
if (token.IsCancellationRequested)
return;

_toasts.Add(item);
StateHasChanged();
// Start the expiration timer only after the toast has actually been queued into the UI,
// so the 3s lifetime begins from when it becomes visible rather than from when Show was
// scheduled (which may run on a non-renderer thread before the add is dispatched).
_ = RemoveAfterDelay(item.Id, cts, token);
});
Comment thread
msynk marked this conversation as resolved.
}

private async Task RemoveAfterDelay(int id, CancellationTokenSource cts, CancellationToken token)
{
try
{
try
{
await Task.Delay(3000, token);
}
catch (OperationCanceledException)
{
return;
}

// Final cancellation check before the dispatcher hop: DisposeAsync may have cancelled the
// token after Task.Delay completed but before we queue the render. Bail out so _toasts and
// StateHasChanged are never touched once teardown has started.
if (token.IsCancellationRequested)
return;

// Mutate the toast list on the renderer's dispatcher to avoid racing the template's foreach.
await InvokeAsync(() =>
{
// Re-check inside the dispatcher callback: DisposeAsync can cancel after the check
// above but before this lambda runs, so no-op once teardown has begun instead of
// mutating _toasts / calling StateHasChanged on a disposed component.
if (token.IsCancellationRequested)
return;

_toasts.RemoveAll(t => t.Id == id);
StateHasChanged();
});
Comment thread
msynk marked this conversation as resolved.
}
finally
{
// Drop the token as soon as its timer finishes (or is cancelled) so _removalTokens
// doesn't grow unbounded on long-lived pages that show many toasts.
bool removed;
lock (_removalTokensLock)
{
removed = _removalTokens.Remove(cts);
}
if (removed)
cts.Dispose();
}
}

public ValueTask DisposeAsync()
{
// Snapshot under the lock: RemoveAfterDelay also removes/disposes tokens as their timers
// complete, so reading the live list here could race with that cleanup.
CancellationTokenSource[] tokens;
lock (_removalTokensLock)
{
tokens = _removalTokens.ToArray();
_removalTokens.Clear();
}

foreach (var cts in tokens)
{
try
{
cts.Cancel();
cts.Dispose();
}
catch (ObjectDisposedException)
{
// Already disposed by RemoveAfterDelay's cleanup; nothing to do.
}
}
return ValueTask.CompletedTask;
}
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>
Loading
Loading