On a phone the Back button left the page under an open menu, sheet or dialog. An open overlay now says so (new IDOverlayHost, cascaded by MainLayout; OverlayService keeps them newest last): DModal and DDropdown through the cascade, so the D components stay free of app services, and the nav menu, PhoneSheet and the full-screen globe through the service. MainLayout's location-changing handler lets Back close the newest overlay instead of leaving the page; a link followed or the app's own navigation (signing out after its question, say) closes them all and goes on. Blazor can't tell Back from the app's navigation, so a capture-phase popstate listener in main.js marks Back (AppStatusService.TookBack). No history entries are pushed. Phone flow: Back closes the menu, a card's More sheet, the composer and the full-screen globe one after another without leaving the feed, and with nothing open goes back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
60 lines
2.1 KiB
Plaintext
60 lines
2.1 KiB
Plaintext
@inherits LocalizableComponentBase
|
|
|
|
@* What opens in place on a desktop (the composer at the top of the feed, a reply under its post) and as a sheet from the
|
|
bottom of a phone's screen (below md), over a dimmed page, with its heading and a close button; a tap outside it or
|
|
Escape closes it too. Class is the inline look from md (padding, a raised panel). *@
|
|
@if (IsOpen)
|
|
{
|
|
<div class="max-md:fixed max-md:inset-0 max-md:z-[60] max-md:flex max-md:flex-col max-md:justify-end" @onkeydown="OnKeyDown">
|
|
<div class="md:hidden absolute inset-0 bg-black/40" aria-hidden="true" @onclick="Close"></div>
|
|
<div class="@($"relative max-md:max-h-[85dvh] max-md:overflow-y-auto max-md:overscroll-contain max-md:rounded-t-box max-md:bg-base-100 max-md:p-4 max-md:pb-[max(1rem,env(safe-area-inset-bottom))] {Class}")"
|
|
data-sheet>
|
|
<div class="md:hidden flex items-center gap-3 mb-3">
|
|
<b class="flex-1 min-w-0 truncate">@Heading</b>
|
|
<button type="button" class="btn btn-sm btn-circle btn-ghost flex-none" aria-label="@Localizer["Close"]" title="@Localizer["Close"]"
|
|
@onclick="Close" data-sheet-close>
|
|
<i class="ion-md-close" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
@ChildContent
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public bool IsOpen { get; set; }
|
|
[Parameter] public EventCallback OnClose { get; set; }
|
|
/// <summary>The sheet's heading on a phone.</summary>
|
|
[Parameter] public string Heading { get; set; }
|
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
|
[Parameter] public string Class { get; set; }
|
|
|
|
[Inject] OverlayService Overlays { get; set; }
|
|
|
|
IDisposable overlay;
|
|
|
|
protected override void OnInitialized() => IsLoading = false;
|
|
|
|
// open, the Back button closes it
|
|
protected override void OnParametersSet()
|
|
{
|
|
if (IsOpen && overlay is null)
|
|
overlay = Overlays.Open(() => InvokeAsync(Close));
|
|
else if (!IsOpen && overlay is not null)
|
|
{
|
|
overlay.Dispose();
|
|
overlay = null;
|
|
}
|
|
}
|
|
|
|
public override async ValueTask DisposeAsync()
|
|
{
|
|
overlay?.Dispose();
|
|
await base.DisposeAsync();
|
|
}
|
|
|
|
Task Close() => OnClose.InvokeAsync();
|
|
|
|
Task OnKeyDown(KeyboardEventArgs e) => e.Key == "Escape" ? Close() : Task.CompletedTask;
|
|
}
|