Files
RolemasterDB/src/RolemasterDb.App/Components/Shell/AppShell.razor

176 lines
4.9 KiB
Plaintext

@implements IDisposable
@inject NavigationManager NavigationManager
<div class="app-shell">
<a class="skip-link" href="#app-main">Skip to main content</a>
<header class="app-shell-header">
<div class="app-shell-bar">
<div class="app-shell-brand-block">
@if (BrandContent is null)
{
<a class="app-shell-brand" href="/">
<span class="app-shell-brand-mark">RM</span>
<span class="app-shell-brand-copy">
<strong>Rolemaster DB</strong>
<span>Reference and lookup workspace</span>
</span>
</a>
}
else
{
@BrandContent
}
</div>
<nav class="app-shell-header-nav" aria-label="Primary">
@if (PrimaryNavContent is null)
{
<ShellPrimaryNav/>
}
else
{
@PrimaryNavContent
}
</nav>
<div class="app-shell-header-omnibox">
@if (OmniboxContent is not null)
{
@OmniboxContent
}
</div>
<div class="app-shell-header-actions">
<button
type="button"
class="app-shell-menu-toggle"
aria-haspopup="dialog"
aria-expanded="@isNavMenuOpen"
aria-controls="app-shell-drawer"
@onclick="ToggleNavMenu">
<span class="app-shell-menu-toggle-bar"></span>
<span class="app-shell-menu-toggle-bar"></span>
<span class="app-shell-menu-toggle-bar"></span>
<span class="visually-hidden">Toggle navigation</span>
</button>
@UtilityContent
</div>
</div>
@if (ShortcutContent is not null)
{
<nav class="app-shell-shortcuts" aria-label="Pinned and recent shortcuts">
@ShortcutContent
</nav>
}
</header>
<ShellOmniboxPalette/>
<main id="app-main" class="app-shell-main">
<div class="content-shell">
@ChildContent
</div>
</main>
@if (isNavMenuOpen)
{
<button
type="button"
class="app-shell-drawer-backdrop"
aria-label="Close navigation"
@onclick="CloseNavMenu">
</button>
<aside id="app-shell-drawer" class="app-shell-drawer" role="dialog" aria-modal="true" aria-label="Primary navigation" @onkeydown="HandleNavMenuKeyDownAsync">
<div class="app-shell-drawer-header">
<strong>Navigate</strong>
<button type="button" class="app-shell-drawer-close" @ref="navMenuCloseButton" @onclick="CloseNavMenu">
Close
</button>
</div>
<ShellPrimaryNav/>
</aside>
}
<nav class="app-shell-mobile-nav" aria-label="Primary">
<ShellPrimaryNav IsBottomNav="true"/>
</nav>
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public RenderFragment? BrandContent { get; set; }
[Parameter]
public RenderFragment? PrimaryNavContent { get; set; }
[Parameter]
public RenderFragment? OmniboxContent { get; set; }
[Parameter]
public RenderFragment? ShortcutContent { get; set; }
[Parameter]
public RenderFragment? UtilityContent { get; set; }
private bool isNavMenuOpen;
private ElementReference navMenuCloseButton;
private bool shouldFocusNavMenuCloseButton;
protected override void OnInitialized()
{
NavigationManager.LocationChanged += HandleLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!shouldFocusNavMenuCloseButton || !isNavMenuOpen)
{
return;
}
shouldFocusNavMenuCloseButton = false;
await navMenuCloseButton.FocusAsync();
}
private void ToggleNavMenu()
{
isNavMenuOpen = !isNavMenuOpen;
shouldFocusNavMenuCloseButton = isNavMenuOpen;
}
private void CloseNavMenu()
{
isNavMenuOpen = false;
shouldFocusNavMenuCloseButton = false;
}
private void HandleLocationChanged(object? sender, LocationChangedEventArgs args)
{
isNavMenuOpen = false;
_ = InvokeAsync(StateHasChanged);
}
public void Dispose()
{
NavigationManager.LocationChanged -= HandleLocationChanged;
}
private Task HandleNavMenuKeyDownAsync(KeyboardEventArgs args)
{
if (string.Equals(args.Key, "Escape", StringComparison.Ordinal))
{
CloseNavMenu();
}
return Task.CompletedTask;
}
}